<!-- build time:Tue Feb 18 2020 17:19:27 GMT+0800 (China Standard Time) --><!DOCTYPE html><html lang="zh"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui"><meta name="renderer" content="webkit"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="format-detection" content="telephone=no,email=no,adress=no"><meta name="theme-color" content="#000000"><meta http-equiv="window-target" content="_top"><title>Spring Boot Redis 入门 | calm&#39;s blog</title><meta name="description" content="摘要: 原创出处 http:&#x2F;&#x2F;www.iocoder.cn&#x2F;Spring-Boot&#x2F;Redis&#x2F; 「芋道源码」欢迎转载，卡姆在此基础上略作修改，保留摘要，谢谢！1. 概述在快速入门 Spring Boot 整合 Redis 之前，我们先来做个简单的了解。在 Spring 的生态中，我们使用 Spring Data Redis 来实现对 Redis 的数据访问。可能这个时候，会有胖友会有疑惑，市面"><meta property="og:type" content="article"><meta property="og:title" content="Spring Boot Redis 入门"><meta property="og:url" content="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/index.html"><meta property="og:site_name" content="卡姆"><meta property="og:description" content="摘要: 原创出处 http:&#x2F;&#x2F;www.iocoder.cn&#x2F;Spring-Boot&#x2F;Redis&#x2F; 「芋道源码」欢迎转载，卡姆在此基础上略作修改，保留摘要，谢谢！1. 概述在快速入门 Spring Boot 整合 Redis 之前，我们先来做个简单的了解。在 Spring 的生态中，我们使用 Spring Data Redis 来实现对 Redis 的数据访问。可能这个时候，会有胖友会有疑惑，市面"><meta property="og:locale" content="zh_CN"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/1.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/2.png"><meta property="article:published_time" content="2020-01-09T13:33:32.000Z"><meta property="article:modified_time" content="2020-01-09T14:56:12.266Z"><meta property="article:author" content="calm"><meta property="article:tag" content="缓存"><meta property="article:tag" content="redis"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/1.png"><link rel="canonical" href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/index.html"><link rel="alternate" href="/atom.xml" title="卡姆" type="application/atom+xml"><link rel="icon" href="/blog/images/avatar.jpg" type="image/x-icon"><link rel="stylesheet" href="/blog/css/style.css"><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.css"><meta name="generator" content="Hexo 4.2.0"></head><body class="main-center theme-green" itemscope itemtype="http://schema.org/WebPage"><header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="slimContent"><div class="navbar-header"><div class="profile-block text-center"><a id="avatar" href="https://github.com/yuyong725" target="_blank"><img class="img-circle img-rotate" src="/blog/images/avatar.jpg" width="200" height="200"></a><h2 id="name" class="hidden-xs hidden-sm">卡姆</h2><h3 id="title" class="hidden-xs hidden-sm hidden-md">a Java coder need calm</h3><small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i> hangzhou, China</small></div><div class="search" id="search-form-wrap"><form class="search-form sidebar-form"><div class="input-group"><input type="text" class="search-form-input form-control" placeholder="搜索"> <span class="input-group-btn"><button type="submit" class="search-form-submit btn btn-flat" onclick="return!1"><i class="icon icon-search"></i></button></span></div></form><div class="ins-search"><div class="ins-search-mask"></div><div class="ins-search-container"><div class="ins-input-wrapper"><input type="text" class="ins-search-input" placeholder="想要查找什么..." x-webkit-speech> <button type="button" class="close ins-close ins-selectable" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button></div><div class="ins-section-wrapper"><div class="ins-section-container"></div></div></div></div></div><button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false"><span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span></button></div><nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation"><ul class="nav navbar-nav main-nav"><li class="menu-item menu-item-home"><a href="/blog/."><i class="icon icon-home-fill"></i> <span class="menu-title">首页</span></a></li><li class="menu-item menu-item-archives"><a href="/blog/archives"><i class="icon icon-archives-fill"></i> <span class="menu-title">归档</span></a></li><li class="menu-item menu-item-categories"><a href="/blog/categories"><i class="icon icon-folder"></i> <span class="menu-title">分类</span></a></li><li class="menu-item menu-item-tags"><a href="/blog/tags"><i class="icon icon-tags"></i> <span class="menu-title">标签</span></a></li><li class="menu-item menu-item-repository"><a href="/blog/repository"><i class="icon icon-project"></i> <span class="menu-title">项目</span></a></li><li class="menu-item menu-item-books"><a href="/blog/books"><i class="icon icon-book-fill"></i> <span class="menu-title">书单</span></a></li><li class="menu-item menu-item-links"><a href="/blog/links"><i class="icon icon-friendship"></i> <span class="menu-title">友链</span></a></li><li class="menu-item menu-item-about"><a href="/blog/about"><i class="icon icon-cup-fill"></i> <span class="menu-title">关于</span></a></li></ul><ul class="social-links"><li><a href="https://github.com/yuyong725" target="_blank" title="Github" data-toggle="tooltip" data-placement="top"><i class="icon icon-github"></i></a></li></ul></nav></div></header><aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><div class="widget"><h3 class="widget-title">公告</h3><div class="widget-body"><div id="board"><div class="content"><p>欢迎交流与分享经验!</p></div></div></div></div><div class="widget"><h3 class="widget-title">分类</h3><div class="widget-body"><ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/blog/categories/CI-DI/">CI/DI</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/git/">git</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/labs/">labs</a><span class="category-list-count">14</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签</h3><div class="widget-body"><ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/MyBatis/" rel="tag">MyBatis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Oauth2/" rel="tag">Oauth2</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Spring-Security/" rel="tag">Spring Security</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/SpringMVC/" rel="tag">SpringMVC</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/docker/" rel="tag">docker</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/dubbo/" rel="tag">dubbo</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/git/" rel="tag">git</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jdbctemplate/" rel="tag">jdbctemplate</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jerkins/" rel="tag">jerkins</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/job/" rel="tag">job</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jpa/" rel="tag">jpa</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/pdf/" rel="tag">pdf</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/redis/" rel="tag">redis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" rel="tag">分库分表</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" rel="tag">多数据源</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" rel="tag">数据库版本管理</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" rel="tag">数据库连接池</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E7%BC%93%E5%AD%98/" rel="tag">缓存</a><span class="tag-list-count">2</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签云</h3><div class="widget-body tagcloud"><a href="/blog/tags/MyBatis/" style="font-size:13px">MyBatis</a> <a href="/blog/tags/Oauth2/" style="font-size:13px">Oauth2</a> <a href="/blog/tags/Spring-Security/" style="font-size:13px">Spring Security</a> <a href="/blog/tags/SpringMVC/" style="font-size:13px">SpringMVC</a> <a href="/blog/tags/docker/" style="font-size:13px">docker</a> <a href="/blog/tags/dubbo/" style="font-size:13px">dubbo</a> <a href="/blog/tags/git/" style="font-size:13px">git</a> <a href="/blog/tags/jdbctemplate/" style="font-size:13px">jdbctemplate</a> <a href="/blog/tags/jerkins/" style="font-size:13px">jerkins</a> <a href="/blog/tags/job/" style="font-size:14px">job</a> <a href="/blog/tags/jpa/" style="font-size:13px">jpa</a> <a href="/blog/tags/pdf/" style="font-size:13px">pdf</a> <a href="/blog/tags/redis/" style="font-size:13px">redis</a> <a href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" style="font-size:13px">分库分表</a> <a href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" style="font-size:13px">多数据源</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" style="font-size:13px">数据库版本管理</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" style="font-size:13px">数据库连接池</a> <a href="/blog/tags/%E7%BC%93%E5%AD%98/" style="font-size:14px">缓存</a></div></div><div class="widget"><h3 class="widget-title">归档</h3><div class="widget-body"><ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/02/">二月 2020</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/01/">一月 2020</a><span class="archive-list-count">8</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2019/12/">十二月 2019</a><span class="archive-list-count">4</span></li></ul></div></div><div class="widget"><h3 class="widget-title">最新文章</h3><div class="widget-body"><ul class="recent-post-list list-unstyled no-thumbnail"><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-async-Job/" class="title">Spring Boot 异步任务入门</a></p><p class="item-date"><time datetime="2020-02-18T05:10:39.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-job/" class="title">Spring Boot 定时任务入门</a></p><p class="item-date"><time datetime="2020-02-18T02:26:31.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-database-version-control/" class="title">Spring Boot 数据库版本管理入门</a></p><p class="item-date"><time datetime="2020-02-17T12:05:54.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-sharding-datasource/" class="title">Spring Boot 分库分表入门</a></p><p class="item-date"><time datetime="2020-02-17T08:15:31.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/13/labs-springboot2-dynamic-datasource/" class="title">Spring Boot 多数据源（读写分离）入门</a></p><p class="item-date"><time datetime="2020-02-13T03:18:37.000Z" itemprop="datePublished">2020-02-13</time></p></div></li></ul></div></div></div></aside><aside class="sidebar sidebar-toc collapse in" id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><nav id="toc" class="article-toc"><h3 class="toc-title">文章目录</h3><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#1-概述"><span class="toc-text">1. 概述</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#2-快速入门"><span class="toc-text">2. 快速入门</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#2-1-引入依赖"><span class="toc-text">2.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-2-配置文件"><span class="toc-text">2.2 配置文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-3-简单测试"><span class="toc-text">2.3 简单测试</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-4-RedisTemplate"><span class="toc-text">2.4 RedisTemplate</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#3-序列化"><span class="toc-text">3. 序列化</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#3-1-RedisSerializer"><span class="toc-text">3.1 RedisSerializer</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1-1-JDK-序列化方式"><span class="toc-text">3.1.1 JDK 序列化方式</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1-2-String-序列化方式"><span class="toc-text">3.1.2 String 序列化方式</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1-3-JSON-序列化方式"><span class="toc-text">3.1.3 JSON 序列化方式</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1-4-XML-序列化方式"><span class="toc-text">3.1.4 XML 序列化方式</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-2-配置序列化方式"><span class="toc-text">3.2 配置序列化方式</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-3-自定义-RedisSerializer-实现类"><span class="toc-text">3.3 自定义 RedisSerializer 实现类</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#4-项目实践"><span class="toc-text">4. 项目实践</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#4-1-Cache-Object"><span class="toc-text">4.1 Cache Object</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-2-数据访问层"><span class="toc-text">4.2 数据访问层</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-3-序列化"><span class="toc-text">4.3 序列化</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#5-示例补充"><span class="toc-text">5. 示例补充</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#5-1-Pipeline"><span class="toc-text">5.1 Pipeline</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-1-1-源码解读"><span class="toc-text">5.1.1 源码解读</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-1-2-具体示例"><span class="toc-text">5.1.2 具体示例</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-2-Transaction"><span class="toc-text">5.2 Transaction</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-1-源码解析"><span class="toc-text">5.2.1 源码解析</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-2-具体示例"><span class="toc-text">5.2.2 具体示例</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-3-补充资料"><span class="toc-text">5.2.3 补充资料</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-4-闲话两句"><span class="toc-text">5.2.4 闲话两句</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-3-Session"><span class="toc-text">5.3 Session</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-3-1-源码解析"><span class="toc-text">5.3.1 源码解析</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-3-2-具体示例"><span class="toc-text">5.3.2 具体示例</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-4-Pub-Sub"><span class="toc-text">5.4 Pub&#x2F;Sub</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-4-1-源码解析"><span class="toc-text">5.4.1 源码解析</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-4-2-具体示例"><span class="toc-text">5.4.2 具体示例</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-4-3-闲话两句"><span class="toc-text">5.4.3 闲话两句</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-5-Script"><span class="toc-text">5.5 Script</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#6-尝试-Redisson"><span class="toc-text">6. 尝试 Redisson</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#6-1-快速入门"><span class="toc-text">6.1 快速入门</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#6-1-1-引入依赖"><span class="toc-text">6.1.1 引入依赖</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-1-2-配置文件"><span class="toc-text">6.1.2 配置文件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-1-3-简单测试"><span class="toc-text">6.1.3 简单测试</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-1-4-闲聊两句"><span class="toc-text">6.1.4 闲聊两句</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#6-2-Redis-分布式锁"><span class="toc-text">6.2 Redis 分布式锁</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#6-3-Redis-限流器"><span class="toc-text">6.3 Redis 限流器</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#6-3-1-具体示例"><span class="toc-text">6.3.1 具体示例</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#666-彩蛋"><span class="toc-text">666. 彩蛋</span></a></li></ol></nav></div></aside><main class="main" role="main"><div class="content"><article id="post-labs-springboot2-redis" class="article article-type-post" itemscope itemtype="http://schema.org/BlogPosting"><div class="article-header"><h1 class="article-title" itemprop="name">Spring Boot Redis 入门</h1><div class="article-meta"><span class="article-date"><i class="icon icon-calendar-check"></i> <a href="/blog/2020/01/09/labs-springboot2-redis/" class="article-date"><time datetime="2020-01-09T13:33:32.000Z" itemprop="datePublished">2020-01-09</time></a></span> <span class="article-category"><i class="icon icon-folder"></i> <a class="article-category-link" href="/blog/categories/labs/">labs</a></span> <span class="article-tag"><i class="icon icon-tags"></i> <a class="article-tag-link" href="/blog/tags/redis/" rel="tag">redis</a>, <a class="article-tag-link" href="/blog/tags/%E7%BC%93%E5%AD%98/" rel="tag">缓存</a></span> <span class="article-read hidden-xs"><i class="icon icon-eye-fill" aria-hidden="true"></i> <span id="busuanzi_container_page_pv"><span id="busuanzi_value_page_pv">0</span></span></span> <span class="post-comment"><i class="icon icon-comment"></i> <a href="/blog/2020/01/09/labs-springboot2-redis/#comments" class="article-comment-link">评论</a></span> <span class="post-wordcount hidden-xs" itemprop="wordCount">字数统计: 13.3k(字)</span> <span class="post-readcount hidden-xs" itemprop="timeRequired">阅读时长: 57(分)</span></div></div><div class="article-entry marked-body" itemprop="articleBody"><blockquote><p>摘要: 原创出处 <a href="http://www.iocoder.cn/Spring-Boot/Redis/" target="_blank" rel="noopener">http://www.iocoder.cn/Spring-Boot/Redis/</a> 「芋道源码」欢迎转载，卡姆在此基础上略作修改，保留摘要，谢谢！</p></blockquote><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>在快速入门 Spring Boot 整合 Redis 之前，我们先来做个简单的了解。在 Spring 的生态中，我们使用 <a href="https://spring.io/projects/spring-data-redis" target="_blank" rel="noopener">Spring Data Redis</a> 来实现对 Redis 的数据访问。</p><p>可能这个时候，会有胖友会有疑惑，市面上已经有 Jedis、Redisson、Lettuce 等优秀的 Java Redis 工具库，为什么还要有 Spring Data Redis 呢？学不动了，头都要秃了！不要慌，我们先来看一张图：<img src="1.png" alt="Spring Data Redis 调用"></p><ul><li>对于下层，Spring Data Redis 提供了统一的操作模板（后文中，我们会看到是 RedisTemplate 类），封装了 Jedis、Lettuce 的 API 操作，访问 Redis 数据。所以，<strong>实际上，Spring Data Redis 内置真正访问的实际是 Jedis、Lettuce 等 API 操作</strong>。</li><li>对于上层，开发者学习如何使用 Spring Data Redis 即可，而无需关心 Jedis、Lettuce 的 API 操作。甚至，未来如果我们想将 Redis 访问从 Jedis 迁移成 Lettuce 来，无需做任何的变动。 相信很多胖友，在选择 Java Redis 工具库，也是有过烦恼的。</li><li>目前，Spring Data Redis 暂时只支持 Jedis、Lettuce 的内部封装，而 Redisson 是由 <a href="https://github.com/redisson/redisson/tree/master/redisson-spring-data" target="_blank" rel="noopener">redisson-spring-data</a> 来提供。</li></ul><p>OK ，哔哔结束，我们先来快速上手下 Spring Data Redis 的使用。</p><h1 id="2-快速入门"><a href="#2-快速入门" class="headerlink" title="2. 快速入门"></a>2. 快速入门</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-data-access/labs-springboot2-redis/labs-springboot2-redis-jedis" target="_blank" rel="noopener">labs-springboot2-redis-jedis</a> 。</p></blockquote><h2 id="2-1-引入依赖"><a href="#2-1-引入依赖" class="headerlink" title="2.1 引入依赖"></a>2.1 引入依赖</h2><p>在 <code>pom.xml</code> 文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-redis-jedis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Spring Data Redis 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 去掉对 Lettuce 的依赖，因为 Spring Boot 优先使用 Lettuce 作为 Redis 客户端 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.lettuce<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lettuce-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 引入 Jedis 的依赖，这样 Spring Boot 实现对 Jedis 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>redis.clients<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jedis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 方便等会写单元测试 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 等会示例会使用 fastjson 作为 JSON 序列化的工具 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>fastjson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.2.61<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- Spring Data Redis 默认使用 Jackson 作为 JSON 序列化的工具 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.fasterxml.jackson.core<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jackson-databind<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">      	<span class="comment">&lt;!--使用 pub/sub 功能会用--&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>commons-io<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>commons-io<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.6<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>具体每个依赖的作用，胖友自己认真看下添加的所有注释噢。</p><h2 id="2-2-配置文件"><a href="#2-2-配置文件" class="headerlink" title="2.2 配置文件"></a>2.2 配置文件</h2><p>在 <code>application.yaml</code> 中，添加 Redis 配置，如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="comment"># 对应 RedisProperties 类</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line">    <span class="attr">password:</span> <span class="comment"># Redis 服务器密码，默认为空。生产中，一定要设置 Redis 密码！</span></span><br><span class="line">    <span class="attr">database:</span> <span class="number">0</span> <span class="comment"># Redis 数据库号，默认为 0 。</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="number">0</span> <span class="comment"># Redis 连接超时时间，单位：毫秒。</span></span><br><span class="line">    <span class="comment"># 对应 RedisProperties.Jedis 内部类</span></span><br><span class="line">    <span class="attr">jedis:</span></span><br><span class="line">      <span class="attr">pool:</span></span><br><span class="line">        <span class="attr">max-active:</span> <span class="number">8</span> <span class="comment"># 连接池最大连接数，默认为 8 。使用负数表示没有限制。</span></span><br><span class="line">        <span class="attr">max-idle:</span> <span class="number">8</span> <span class="comment"># 默认连接数最小空闲的连接数，默认为 8 。使用负数表示没有限制。</span></span><br><span class="line">        <span class="attr">min-idle:</span> <span class="number">0</span> <span class="comment"># 默认连接池最小空闲的连接数，默认为 0 。允许设置 0 和 正数。</span></span><br><span class="line">        <span class="attr">max-wait:</span> <span class="number">-1</span> <span class="comment"># 连接池最大阻塞等待时间，单位：毫秒。默认为 -1 ，表示不限制。</span></span><br></pre></td></tr></table></figure><p>具体每个参数的作用，胖友自己认真看下添加的所有注释噢。</p><ul><li>这里有个有意思的事情，本地redis没设置密码的话，如果配置文件添加了<code>spring.redis.password</code> 属性，而没有赋值，jedis不会报错，redisson会报错<code>ERR Client sent AUTH, but no password is set</code></li></ul><h2 id="2-3-简单测试"><a href="#2-3-简单测试" class="headerlink" title="2.3 简单测试"></a>2.3 简单测试</h2><p>创建 <code>JedisTest</code> 测试类，我们来测试一下简单的 SET 指令。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span></span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">JedisTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testStringSetKey</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        stringRedisTemplate.opsForValue().set(<span class="string">"string-calm"</span>, <span class="string">"handsome"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过 StringRedisTemplate 类，我们进行了一次 Redis SET 指令的执行。关于 StringRedisTemplate 是什么，我们先卖个关子，在 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/">「2.4 RedisTemplate」</a> 中来介绍。</p><p>我们先来执行下 <code>#testStringSetKey()</code> 方法这个测试方法。执行完成后，我们在控制台查询，看看是否真的执行成功了。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ redis-cli get string-calm</span><br><span class="line"><span class="string">"handsome"</span></span><br></pre></td></tr></table></figure><h2 id="2-4-RedisTemplate"><a href="#2-4-RedisTemplate" class="headerlink" title="2.4 RedisTemplate"></a>2.4 RedisTemplate</h2><p><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/RedisTemplate.java" target="_blank" rel="noopener">RedisTemplate</a> 类，从类名上，我们就明明白白知道，提供 Redis 操作模板 API 。核心属性如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisTemplate.java</span></span><br><span class="line"><span class="comment">// 艿艿省略了一些不重要的属性。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// &lt;1&gt; 序列化相关属性</span></span><br><span class="line"><span class="meta">@SuppressWarnings</span>(<span class="string">"rawtypes"</span>) <span class="keyword">private</span> <span class="meta">@Nullable</span> RedisSerializer keySerializer = <span class="keyword">null</span>;</span><br><span class="line"><span class="meta">@SuppressWarnings</span>(<span class="string">"rawtypes"</span>) <span class="keyword">private</span> <span class="meta">@Nullable</span> RedisSerializer valueSerializer = <span class="keyword">null</span>;</span><br><span class="line"><span class="meta">@SuppressWarnings</span>(<span class="string">"rawtypes"</span>) <span class="keyword">private</span> <span class="meta">@Nullable</span> RedisSerializer hashKeySerializer = <span class="keyword">null</span>;</span><br><span class="line"><span class="meta">@SuppressWarnings</span>(<span class="string">"rawtypes"</span>) <span class="keyword">private</span> <span class="meta">@Nullable</span> RedisSerializer hashValueSerializer = <span class="keyword">null</span>;</span><br><span class="line"><span class="keyword">private</span> RedisSerializer&lt;String&gt; stringSerializer = RedisSerializer.string();</span><br><span class="line"></span><br><span class="line"><span class="comment">// &lt;2&gt; Lua 脚本执行器</span></span><br><span class="line"><span class="keyword">private</span> <span class="meta">@Nullable</span> ScriptExecutor&lt;K&gt; scriptExecutor;</span><br><span class="line"></span><br><span class="line"><span class="comment">// &lt;3&gt; 常见数据结构操作类</span></span><br><span class="line"><span class="comment">// cache singleton objects (where possible)</span></span><br><span class="line"><span class="keyword">private</span> <span class="meta">@Nullable</span> ValueOperations&lt;K, V&gt; valueOps;</span><br><span class="line"><span class="keyword">private</span> <span class="meta">@Nullable</span> ListOperations&lt;K, V&gt; listOps;</span><br><span class="line"><span class="keyword">private</span> <span class="meta">@Nullable</span> SetOperations&lt;K, V&gt; setOps;</span><br><span class="line"><span class="keyword">private</span> <span class="meta">@Nullable</span> ZSetOperations&lt;K, V&gt; zSetOps;</span><br><span class="line"><span class="keyword">private</span> <span class="meta">@Nullable</span> GeoOperations&lt;K, V&gt; geoOps;</span><br><span class="line"><span class="keyword">private</span> <span class="meta">@Nullable</span> HyperLogLogOperations&lt;K, V&gt; hllOps;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1&gt;</code> 处，看到了四个序列化相关的属性，用于 KEY 和 VALUE 的序列化。<ul><li>例如说，我们在使用 POJO 对象存储到 Redis 中，一般情况下，会使用 JSON 方式序列化成字符串，存储到 Redis 中。详细的，我们在 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/">「3. 序列化」</a> 小节中来说明。</li><li>在上文中，我们看到了 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/StringRedisTemplate.java" target="_blank" rel="noopener">StringRedisTemplate</a> 类，它继承 RedisTemplate 类，使用 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/serializer/StringRedisSerializer.java" target="_blank" rel="noopener">StringRedisSerializer</a> 字符串序列化方式。直接点开 StringRedisSerializer 源码，看下它的构造方法，瞬间明明白白。</li></ul></li><li><code>&lt;2&gt;</code> 处，Lua 脚本执行器，提供 <a href="http://redis.cn/commands.html#scripting" target="_blank" rel="noopener">Redis scripting</a> API 操作。</li><li><code>&lt;3&gt;</code> 处，Redis 常见数据结构操作类。<ul><li><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/ValueOperations.java" target="_blank" rel="noopener">ValueOperations</a> 类，提供 <a href="http://redis.cn/commands.html#string" target="_blank" rel="noopener">Redis String</a> API 操作。</li><li><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/ListOperations.java" target="_blank" rel="noopener">ListOperations</a> 类，提供 <a href="http://redis.cn/commands.html#list" target="_blank" rel="noopener">Redis List</a> API 操作。</li><li><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/SetOperations.java" target="_blank" rel="noopener">SetOperations</a> 类，提供 <a href="http://redis.cn/commands.html#set" target="_blank" rel="noopener">Redis Set</a> API 操作。</li><li><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/ZSetOperations.java" target="_blank" rel="noopener">ZSetOperations</a> 类，提供 <a href="http://redis.cn/commands.html#sorted_set" target="_blank" rel="noopener">Redis ZSet(Sorted Set)</a> API 操作。</li><li><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/GeoOperations.java" target="_blank" rel="noopener">GeoOperations</a> 类，提供 <a href="http://redis.cn/commands.html#geo" target="_blank" rel="noopener">Redis Geo</a> API 操作。</li><li><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/HyperLogLogOperations.java" target="_blank" rel="noopener">HyperLogLogOperations</a> 类，提供 <a href="http://redis.cn/commands.html#hyperloglog" target="_blank" rel="noopener">Redis HyperLogLog</a> API 操作。</li></ul></li></ul><p>那么 Pub/Sub、Transaction、Pipeline、Keys、Cluster、Connection 等相关的 API 操作呢？它在 RedisTemplate 自身提供，因为它们不属于具体每一种数据结构，所以没有封装在对应的 Operations 类中。哈哈哈，胖友打开 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/RedisTemplate.java" target="_blank" rel="noopener">RedisTemplate</a> 类，去瞅瞅，妥妥的明白。</p><h1 id="3-序列化"><a href="#3-序列化" class="headerlink" title="3. 序列化"></a>3. 序列化</h1><blockquote><p>为了尽量把序列化说的清楚一些，所以本小节内容会略长。</p><p>因为有些地方，直接撸源码，比吓哔哔一段话更易懂，所以会有一些源码，保持淡定。</p></blockquote><h2 id="3-1-RedisSerializer"><a href="#3-1-RedisSerializer" class="headerlink" title="3.1 RedisSerializer"></a>3.1 RedisSerializer</h2><p><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/serializer/RedisSerializer.java" target="_blank" rel="noopener">RedisSerializer</a> 接口，Redis 序列化接口，用于 Redis KEY 和 VALUE 的序列化。简化代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisSerializer.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">RedisSerializer</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Nullable</span></span><br><span class="line">	<span class="keyword">byte</span>[] serialize(<span class="meta">@Nullable</span> T t) <span class="keyword">throws</span> SerializationException;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Nullable</span></span><br><span class="line">	<span class="function">T <span class="title">deserialize</span><span class="params">(@Nullable <span class="keyword">byte</span>[] bytes)</span> <span class="keyword">throws</span> SerializationException</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>定义了对象 <code>&lt;T&gt;</code> 和二进制数组的转换。</li><li>啊，可能有胖友会有疑惑了：我们在 <code>redis-cli</code> 终端，看到的不都是字符串么，怎么这里是序列化成二进制数组呢？实际上，Redis Client 传递给 Redis Server 是传递的 KEY 和 VALUE 都是二进制值数组。好奇的胖友，可以打开 Jedis <a href="https://github.com/xetorthio/jedis/blob/master/src/main/java/redis/clients/jedis/Connection.java#L123" target="_blank" rel="noopener">Connection#sendCommand</a> 方法，传入的参数就是二进制数组，而 <code>cmd</code> 命令也会被序列化成二进制数组。</li></ul><p>RedisSerializer 的实现类，如下图：<img src="2.png" alt="Spring Data Redis 调用"></p><p>主要分成四类：</p><ul><li>JDK 序列化方式</li><li>String 序列化方式</li><li>JSON 序列化方式</li><li>XML 序列化方式</li></ul><h3 id="3-1-1-JDK-序列化方式"><a href="#3-1-1-JDK-序列化方式" class="headerlink" title="3.1.1 JDK 序列化方式"></a>3.1.1 JDK 序列化方式</h3><p><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/serializer/JdkSerializationRedisSerializer.java" target="_blank" rel="noopener">JdkSerializationRedisSerializer</a> ，默认情况下，RedisTemplate 使用该数据列化方式。具体的，可以看看 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/RedisTemplate.java" target="_blank" rel="noopener">RedisTemplate#afterPropertiesSet()</a> 方法，在 RedisTemplate 未设置序列化的情况下，使用 JdkSerializationRedisSerializer 作为序列化实现。在 Spring Boot 自动化配置 RedisTemplate Bean 对象时，就未设置。</p><p>绝大多数情况下，可能 99.9999% ，我们不会使用 JdkSerializationRedisSerializer 进行序列化。为什么呢？我们来看一个示例，代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// JedisTest.java</span></span><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span></span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">JedisTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSetKey</span><span class="params">()</span> </span>&#123;</span><br><span class="line">				redisTemplate.opsForValue().set(<span class="string">"calm"</span>, <span class="string">"handsome"</span>);</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们先来执行下 <code>#testSetKey()</code> 方法这个测试方法。注意，此处我们使用的是 RedisTemplate 而不是 StringRedisTemplate 。执行完成后，我们在控制台查询，看看是否真的执行成功了。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在 `redis-cli` 终端中</span></span><br><span class="line"></span><br><span class="line">127.0.0.1:6379&gt; scan 0</span><br><span class="line">1) <span class="string">"0"</span></span><br><span class="line">2) 1) <span class="string">"\xac\xed\x00\x05t\x00\x05calm"</span></span><br><span class="line"></span><br><span class="line">127.0.0.1:6379&gt; get <span class="string">"\xac\xed\x00\x05t\x00\x05calm"</span></span><br><span class="line"><span class="string">"\xac\xed\x00\x05t\x00\x05handsome"</span></span><br></pre></td></tr></table></figure><ul><li>具体为什么是这样一串奇怪的 16 进制，胖友可以看看 <a href="https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/java/io/ObjectOutputStream.java#L1301-L1311" target="_blank" rel="noopener">ObjectOutputStream#writeString(String str, boolean unshared)</a> 的代码，实际就是标志位 + 字符串长度 + 字符串内容。</li><li>如果胖友直接复制我的github代码，跑起来发现并没有这些奇怪的16进制，这是因为我在 <code>RedisConfiguration</code> 自定义了key的序列化方式，因此建议胖友尽量自己写，或者根据文章进度复制指定的类。</li></ul><p>对于 KEY 被序列化成这样，我们线上通过 KEY 去查询对应的 VALUE 势必会非常不方便，所以 KEY 肯定是不能被这样序列化的。</p><p>对于 VALUE 被序列化成这样，除了阅读可能困难一点，不支持跨语言外，实际上也没啥问题。不过，实际线上场景，还是使用 JSON 序列化居多。</p><h3 id="3-1-2-String-序列化方式"><a href="#3-1-2-String-序列化方式" class="headerlink" title="3.1.2 String 序列化方式"></a>3.1.2 String 序列化方式</h3><p>① <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/serializer/StringRedisSerializer.java" target="_blank" rel="noopener">StringRedisSerializer</a> ，字符串和二进制数组的<strong>直接</strong>转换。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// StringRedisSerializer.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Charset charset;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">deserialize</span><span class="params">(@Nullable <span class="keyword">byte</span>[] bytes)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> (bytes == <span class="keyword">null</span> ? <span class="keyword">null</span> : <span class="keyword">new</span> String(bytes, charset));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">byte</span>[] serialize(<span class="meta">@Nullable</span> String string) &#123;</span><br><span class="line">	<span class="keyword">return</span> (string == <span class="keyword">null</span> ? <span class="keyword">null</span> : string.getBytes(charset));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>是不是很直接简单。</li></ul><p><strong>绝大多数情况下，我们 KEY 和 VALUE 都会使用这种序列化方案</strong>。而 VALUE 的序列化和反序列化，自己在逻辑调用 JSON 方法去序列化。为什么呢？继续往下看。</p><p>② <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/serializer/GenericToStringSerializer.java" target="_blank" rel="noopener">GenericToStringSerializer</a> ，使用 Spring <a href="https://codeday.me/bug/20180111/117294.html" target="_blank" rel="noopener">ConversionService</a> 实现 <code>&lt;T&gt;</code> 对象和 String 的转换，从而 String 和二进制数组的转换。</p><p>例如说，序列化的过程，首先 <code>&lt;T&gt;</code> 对象通过 ConversionService 转换成 String ，然后 String 再序列化成二进制数组。反序列化的过程，胖友自己结合源码思考下 🤔 。</p><p>当然，GenericToStringSerializer 貌似基本不会去使用，所以不用去了解也问题不大，哈哈哈。</p><h3 id="3-1-3-JSON-序列化方式"><a href="#3-1-3-JSON-序列化方式" class="headerlink" title="3.1.3 JSON 序列化方式"></a>3.1.3 JSON 序列化方式</h3><p>① <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java" target="_blank" rel="noopener">GenericJackson2JsonRedisSerializer</a> ，使用 Jackson 实现 JSON 的序列化方式，并且从 Generic 单词可以看出，是支持所有类。怎么体现呢？参见构造方法的代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GenericJackson2JsonRedisSerializer.java</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">GenericJackson2JsonRedisSerializer</span><span class="params">(@Nullable String classPropertyTypeName)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">this</span>(<span class="keyword">new</span> ObjectMapper());</span><br><span class="line"></span><br><span class="line">	<span class="comment">// simply setting &#123;@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)&#125; does not help here since we need</span></span><br><span class="line">	<span class="comment">// the type hint embedded for deserialization using the default typing feature.</span></span><br><span class="line">	mapper.registerModule(<span class="keyword">new</span> SimpleModule().addSerializer(<span class="keyword">new</span> NullValueSerializer(classPropertyTypeName)));</span><br><span class="line"></span><br><span class="line">	<span class="comment">// &lt;1&gt;</span></span><br><span class="line">	<span class="keyword">if</span> (StringUtils.hasText(classPropertyTypeName)) &#123;</span><br><span class="line">		mapper.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, classPropertyTypeName);</span><br><span class="line">	<span class="comment">// &lt;2&gt;</span></span><br><span class="line">	&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">		mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1&gt;</code> 处，如果传入了 <code>classPropertyTypeName</code> 属性，就是使用使用传入对象的 <code>classPropertyTypeName</code> 属性对应的值，作为默认类型（Default Typing）。</li><li><code>&lt;2&gt;</code> 处，如果未传入 <code>classPropertyTypeName</code> 属性，则使用传入对象的类全名，作为默认类型（Default Typing）。</li></ul><p>那么，胖友可能会问题，什么是<strong>默认类型（Default Typing）</strong>呢？我们来思考下，在将一个对象序列化成一个字符串，怎么保证字符串反序列化成对象的类型呢？Jackson 通过 Default Typing ，会在字符串多冗余一个类型，这样反序列化就知道具体的类型了。来举个例子，使用我们等会示例会用到的 <code>UserCacheObject</code> 类。</p><ul><li><p>标准序列化的结果，如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">"id"</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="attr">"name"</span>: <span class="string">"calm testServiceSet"</span>,</span><br><span class="line">    <span class="attr">"gender"</span>: <span class="number">1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>使用 Jackson Default Typing 机制序列化的结果，如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">       <span class="attr">"@class"</span>: <span class="string">"cn.javadog.labs.springboot2.redis.jedis.cacheobject"</span>,</span><br><span class="line">       <span class="attr">"id"</span>: <span class="number">1</span>,</span><br><span class="line">       <span class="attr">"name"</span>: <span class="string">"calm testServiceSet"</span>,</span><br><span class="line">       <span class="attr">"gender"</span>: <span class="number">1</span></span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><ul><li>看 <code>@class</code> 属性，反序列化的对象的类型不就有了么？</li></ul></li></ul><p>下面我们来看一个 GenericJackson2JsonRedisSerializer 的示例。在看之前，胖友先跳到 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/">「3.2 配置序列化方式」</a> 小节，来看看如何配置 GenericJackson2JsonRedisSerializer 作为 VALUE 的序列化方式。然后，马上调回到此处。</p><p>示例代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// JedisTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testStringSetKeyUserCache</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	UserCacheObject object = <span class="keyword">new</span> UserCacheObject()</span><br><span class="line">		.setId(<span class="number">1</span>)</span><br><span class="line">		.setName(<span class="string">"calm springboot2"</span>)</span><br><span class="line">		.setGender(<span class="number">1</span>); <span class="comment">// 男</span></span><br><span class="line">	String key = String.format(<span class="string">"user:%d"</span>, object.getId());</span><br><span class="line">	redisTemplate.opsForValue().set(key, object);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testStringGetKeyUserCache</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	String key = String.format(<span class="string">"user:%d"</span>, <span class="number">1</span>);</span><br><span class="line">	Object value = redisTemplate.opsForValue().get(key);</span><br><span class="line">	System.out.println(value);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>胖友分别执行 <code>#testStringSetKeyUserCache()</code> 和 <code>#testStringGetKeyUserCache()</code> 方法，然后对着 Redis 的结果看看，比较简单，就不多哔哔了。</p><p>我们在回过头来看看 <code>@class</code> 属性，它看似完美解决了反序列化后的对象类型，但是带来 JSON 字符串占用变大，所以实际项目中，我们也并不会采用 Jackson2JsonRedisSerializer 类。</p><p><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java" target="_blank" rel="noopener">GenericJackson2JsonRedisSerializer</a> ，使用 Jackson 实现 JSON 的序列化方式，并且显示指定 <code>&lt;T&gt;</code> 类型。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Jackson2JsonRedisSerializer.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Jackson2JsonRedisSerializer</span>&lt;<span class="title">T</span>&gt; <span class="keyword">implements</span> <span class="title">RedisSerializer</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">    <span class="comment">// ... 省略不重要的代码</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 指定类型，和 &lt;T&gt; 要一致。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> JavaType javaType;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> ObjectMapper objectMapper = <span class="keyword">new</span> ObjectMapper();</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因为 Jackson2JsonRedisSerializer 序列化类里已经声明了类型，所以序列化的 JSON 字符串，无需在存储一个 <code>@class</code> 属性，用于存储类型。</p><p>但是，我们抠脚一想，如果使用 Jackson2JsonRedisSerializer 作为序列化实现类，那么如果我们类型比较多，岂不是每个类型都要定义一个 RedisTemplate Bean 了？！不然拿到的就只能是<code>object</code>，所以实际场景下，我们也并不会使用 Jackson2JsonRedisSerializer 类。</p><ul><li>注意，GenericFastJsonRedisSerializer 不是 Spring Data Redis 内置实现，而是由于 Jackson 自己实现。因此需要引入相关的依赖</li></ul><h3 id="3-1-4-XML-序列化方式"><a href="#3-1-4-XML-序列化方式" class="headerlink" title="3.1.4 XML 序列化方式"></a>3.1.4 XML 序列化方式</h3><p><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/serializer/OxmSerializer.java" target="_blank" rel="noopener">OxmSerializer</a> ，使用 Spring <a href="https://www.jianshu.com/p/1b7c8dc26001" target="_blank" rel="noopener">OXM</a> 实现将对象和 String 的转换，从而 String 和二进制数组的转换。</p><p>因为 XML 序列化方式，暂时没有这么干过，我自己也没有，所以就直接忽略它吧。😝</p><h2 id="3-2-配置序列化方式"><a href="#3-2-配置序列化方式" class="headerlink" title="3.2 配置序列化方式"></a>3.2 配置序列化方式</h2><p>创建 RedisConfiguration 配置类，代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisConfiguration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> RedisTemplate&lt;String, Object&gt; <span class="title">redisTemplate</span><span class="params">(RedisConnectionFactory factory)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 创建 RedisTemplate 对象</span></span><br><span class="line">        RedisTemplate&lt;String, Object&gt; template = <span class="keyword">new</span> RedisTemplate&lt;&gt;();</span><br><span class="line">        <span class="comment">// 设置 RedisConnection 工厂。它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友，可以自己去撸下。</span></span><br><span class="line">        template.setConnectionFactory(factory);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 使用 String 序列化方式，序列化 KEY 。</span></span><br><span class="line">        template.setKeySerializer(RedisSerializer.string());</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 使用 JSON 序列化方式（库是 Jackson ），序列化 VALUE 。</span></span><br><span class="line">        template.setValueSerializer(RedisSerializer.json());</span><br><span class="line">        <span class="keyword">return</span> template;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>RedisSerializer#string()</code> 静态方法，返回的就是使用 UTF-8 编码的 StringRedisSerializer 对象。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisSerializer.java</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> RedisSerializer&lt;String&gt; <span class="title">string</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> StringRedisSerializer.UTF_8;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// StringRedisSerializer.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> StringRedisSerializer ISO_8859_1 = <span class="keyword">new</span> StringRedisSerializer(StandardCharsets.ISO_8859_1);</span><br></pre></td></tr></table></figure></li><li><p><code>RedisSerializer#json()</code> 静态方法，返回 GenericJackson2JsonRedisSerializer 对象。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisSerializer.java</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> RedisSerializer&lt;Object&gt; <span class="title">json</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">new</span> GenericJackson2JsonRedisSerializer();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h2 id="3-3-自定义-RedisSerializer-实现类"><a href="#3-3-自定义-RedisSerializer-实现类" class="headerlink" title="3.3 自定义 RedisSerializer 实现类"></a>3.3 自定义 RedisSerializer 实现类</h2><p>我们直接以 GenericFastJsonRedisSerializer 举例子，直接莽源码。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GenericFastJsonRedisSerializer.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GenericFastJsonRedisSerializer</span> <span class="keyword">implements</span> <span class="title">RedisSerializer</span>&lt;<span class="title">Object</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> ParserConfig defaultRedisConfig = <span class="keyword">new</span> ParserConfig();</span><br><span class="line">    <span class="keyword">static</span> &#123; defaultRedisConfig.setAutoTypeSupport(<span class="keyword">true</span>);&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">byte</span>[] serialize(Object object) <span class="keyword">throws</span> SerializationException &#123;</span><br><span class="line">        <span class="comment">// 空，直接返回空数组</span></span><br><span class="line">        <span class="keyword">if</span> (object == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">0</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 使用 JSON 进行序列化成二进制数组，同时通过 SerializerFeature.WriteClassName 参数，声明写入类全名。</span></span><br><span class="line">            <span class="keyword">return</span> JSON.toJSONBytes(object, SerializerFeature.WriteClassName);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> SerializationException(<span class="string">"Could not serialize: "</span> + ex.getMessage(), ex);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">deserialize</span><span class="params">(<span class="keyword">byte</span>[] bytes)</span> <span class="keyword">throws</span> SerializationException </span>&#123;</span><br><span class="line">        <span class="comment">// 如果为空，则返回空对象</span></span><br><span class="line">        <span class="keyword">if</span> (bytes == <span class="keyword">null</span> || bytes.length == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 使用 JSON 解析成对象。</span></span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(<span class="keyword">new</span> String(bytes, IOUtils.UTF8), Object<span class="class">.<span class="keyword">class</span>, <span class="title">defaultRedisConfig</span>)</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> SerializationException(<span class="string">"Could not deserialize: "</span> + ex.getMessage(), ex);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>完成自定义 RedisSerializer 配置类后，我们就可以参照 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/">「3.2 配置序列化方式」</a> 小节，将 VALUE 序列化的修改成我们的，哈哈哈。</p><h1 id="4-项目实践"><a href="#4-项目实践" class="headerlink" title="4. 项目实践"></a>4. 项目实践</h1><p>本小节，我们来分享我们在生产中的一些实践。关于这块，希望大家可以一起讨论，能够让我们的代码更加优雅干净。</p><h2 id="4-1-Cache-Object"><a href="#4-1-Cache-Object" class="headerlink" title="4.1 Cache Object"></a>4.1 Cache Object</h2><p>在我们使用数据库时，我们会创建 <code>dataobject</code> 包，存放 DO（Data Object）数据库实体对象。</p><p>那么同理，我们缓存对象，怎么进行对应呢？对于复杂的缓存对象，我们创建了 <code>cacheobject</code> 包，和 <code>dataobject</code> 包同层。如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">service # 业务逻辑层</span><br><span class="line">dao # 数据库访问层</span><br><span class="line">dataobject # DO</span><br><span class="line">cacheobject # 缓存对象</span><br></pre></td></tr></table></figure><p>并且所有的 Cache Object 对象使用 CacheObject 结尾，例如说 UserCacheObject、ProductCacheObject 。</p><h2 id="4-2-数据访问层"><a href="#4-2-数据访问层" class="headerlink" title="4.2 数据访问层"></a>4.2 数据访问层</h2><p>在我们访问数据库时，我们会创建 <code>dao</code> 包，存放每个 DO 对应的 Dao 对应。那么对于每一个 CacheObject 类，我们也会创建一个其对应的 Dao 类。例如说，UserCacheObject 对应 UserCacheObjectDao 类。示例代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Repository</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserCacheDao</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEY_PATTERN = <span class="string">"user:%d"</span>; <span class="comment">// user:用户编号 &lt;1&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span>(name = <span class="string">"redisTemplate"</span>)</span><br><span class="line">    <span class="meta">@SuppressWarnings</span>(<span class="string">"SpringJavaInjectionPointsAutowiringInspection"</span>)</span><br><span class="line">    <span class="keyword">private</span> ValueOperations&lt;String, String&gt; operations; <span class="comment">// &lt;2&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> String <span class="title">buildKey</span><span class="params">(Integer id)</span> </span>&#123; <span class="comment">// &lt;3&gt;</span></span><br><span class="line">        <span class="keyword">return</span> String.format(KEY_PATTERN, id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> UserCacheObject <span class="title">get</span><span class="params">(Integer id)</span> </span>&#123;</span><br><span class="line">        String key = buildKey(id);</span><br><span class="line">        String value = operations.get(key);</span><br><span class="line">        <span class="keyword">return</span> JSONUtil.parseObject(value, UserCacheObject<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(Integer id, UserCacheObject object)</span> </span>&#123;</span><br><span class="line">        String key = buildKey(id);</span><br><span class="line">        String value = JSONUtil.toJSONString(object);</span><br><span class="line">        operations.set(key, value);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1&gt;</code> 处，通过静态变量，声明 KEY 的前缀，并且使用冒号作为间隔</li><li><code>&lt;2&gt;</code> 处，通过 <code>@Resource</code> 注入指定名字的 RedisTemplate 对应的 Operations 对象，这样明确每个 KEY 的类型。<ul><li>这种用法卡姆也是第一次遇到，相当神奇</li></ul></li><li><code>&lt;3&gt;</code> 处，声明 <code>KEY_PATTERN</code> 对应的 KEY 拼接方法，避免散落在每个方法中。</li><li>剩余的，就是每个方法封装对应的操作。</li></ul><p>可能会有胖友问，为什么不支持将 RedisTemplate 直接 Service 业务层调用呢？如果这样，我们业务代码里，就容易混杂着很多 Redis 访问代码的细节，导致很脏乱。我们试着把 RedisTemplate 想象成 Spring JDBCTemplate ，我们一定会声明对应的 Dao 类，访问数据库。所以，同理落。</p><p>那么还有一个问题，UserCacheDao 放在哪个包下？目前的想法是，将 <code>dao</code> 包下拆成 <code>mysql</code>、<code>redis</code> 包。这样，MySQL 相关的 Dao 放在 <code>mysql</code> 包下，Redis 相关的 Dao 放在 <code>redis</code> 。</p><h2 id="4-3-序列化"><a href="#4-3-序列化" class="headerlink" title="4.3 序列化"></a>4.3 序列化</h2><p>在 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/">「3. 序列化」</a> 小节中，我们仔细翻看了每个序列化方式，暂时没有一个能够完美的契合我们的需求，所以我们直接使用最简单的 <strong>StringRedisSerializer</strong> 作为序列化实现类。而真正的序列化，我们在各个 Dao 类里，自己<strong>手动</strong>来调用。</p><p>例如说，在 UserCacheDao 示例中，已经看到了这么做了。这里还有一个细化点，虽然我们是自己<strong>手动</strong>序列化，可以自己简单封装一个 <code>JSONUtil</code> 类，未来如果我们想换 JSON 库，就比较方便了。其实，这个和 Spring Data Redis 所做的封装是一个思路。</p><h1 id="5-示例补充"><a href="#5-示例补充" class="headerlink" title="5. 示例补充"></a>5. 示例补充</h1><p>像 String、List、Set、ZSet、Geo、HyperLogLog 等等数据结构的操作，胖友自己去用用对应的 Operations 操作类的 API 方法，就非常容易懂了，我们更多的，补充 Pipeline、Transaction、Pub/Sub、Script 等等功能的示例。</p><h2 id="5-1-Pipeline"><a href="#5-1-Pipeline" class="headerlink" title="5.1 Pipeline"></a>5.1 Pipeline</h2><p>如果胖友没有了解过 Redis 的 Pipeline 机制，可以看看 <a href="http://redis.cn/topics/pipelining.html" target="_blank" rel="noopener">《Redis 文档 —— Pipeline》</a> 文章，批量操作，提升性能必备神器。</p><p>在 RedisTemplate 类中，提供了 2 组四个方法，用于执行 Redis Pipeline 操作。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// &lt;1&gt; 基于 Session 执行 Pipeline</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;Object&gt; <span class="title">executePipelined</span><span class="params">(SessionCallback&lt;?&gt; session)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> executePipelined(session, valueSerializer);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;Object&gt; <span class="title">executePipelined</span><span class="params">(SessionCallback&lt;?&gt; session, @Nullable RedisSerializer&lt;?&gt; resultSerializer)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// ... 省略代码</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// &lt;2&gt; 直接执行 Pipeline</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;Object&gt; <span class="title">executePipelined</span><span class="params">(RedisCallback&lt;?&gt; action)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> executePipelined(action, valueSerializer);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;Object&gt; <span class="title">executePipelined</span><span class="params">(RedisCallback&lt;?&gt; action, @Nullable RedisSerializer&lt;?&gt; resultSerializer)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// ... 省略代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>两组方法的差异，在于是否是 Session 中执行。那么 Session 是什么呢？卖个关子，在 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/">「5.3 Session」</a> 中来详细解析。本小节，我们只讲 Pipeline + RedisCallback 的组合的方法。</li><li>每组方法里，差别在于是否传入 RedisSerializer 参数。如果不传，则使用 RedisTemplate 自己的序列化相关的属性。</li></ul><h3 id="5-1-1-源码解读"><a href="#5-1-1-源码解读" class="headerlink" title="5.1.1 源码解读"></a>5.1.1 源码解读</h3><p>在看具体的 <code>#executePipelined(RedisCallback action, ...)</code> 方法的示例之前，我们先来看一波源码，这样我们才能更好的理解具体的使用方法。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisTemplate.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;Object&gt; <span class="title">executePipelined</span><span class="params">(RedisCallback&lt;?&gt; action, @Nullable RedisSerializer&lt;?&gt; resultSerializer)</span> </span>&#123;</span><br><span class="line">	<span class="comment">// &lt;1&gt; 执行 Redis 方法</span></span><br><span class="line">	<span class="keyword">return</span> execute((RedisCallback&lt;List&lt;Object&gt;&gt;) connection -&gt; &#123;</span><br><span class="line">		<span class="comment">// &lt;2&gt; 打开 pipeline</span></span><br><span class="line">		connection.openPipeline();</span><br><span class="line">		<span class="keyword">boolean</span> pipelinedClosed = <span class="keyword">false</span>; <span class="comment">// 标记 pipeline 是否关闭</span></span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			<span class="comment">// &lt;3&gt; 执行</span></span><br><span class="line">			Object result = action.doInRedis(connection);</span><br><span class="line">			<span class="comment">// &lt;4&gt; 不要返回结果</span></span><br><span class="line">			<span class="keyword">if</span> (result != <span class="keyword">null</span>) &#123;</span><br><span class="line">				<span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataAccessApiUsageException(</span><br><span class="line">						<span class="string">"Callback cannot return a non-null value as it gets overwritten by the pipeline"</span>);</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="comment">// &lt;5&gt; 提交 pipeline 执行</span></span><br><span class="line">			List&lt;Object&gt; closePipeline = connection.closePipeline();</span><br><span class="line">			pipelinedClosed = <span class="keyword">true</span>;</span><br><span class="line">			<span class="comment">// &lt;6&gt; 反序列化结果，并返回</span></span><br><span class="line">			<span class="keyword">return</span> deserializeMixedResults(closePipeline, resultSerializer, hashKeySerializer, hashValueSerializer);</span><br><span class="line">		&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">			<span class="keyword">if</span> (!pipelinedClosed) &#123;</span><br><span class="line">				connection.closePipeline();</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1&gt;</code> 处，调用 <code>#execute(RedisCallback action)</code> 方法，执行 Redis 方法。注意，此处传入的 <code>action</code> 参数，不是我们传入的 RedisCallback 参数。我们的会在该 <code>action</code> 中被执行。</li><li><code>&lt;2&gt;</code> 处，调用 <code>RedisConnection#openPipeline()</code> 方法，<strong>自动</strong>打开 Pipeline 模式。这样，我们就不需要手动去打开了。</li><li><code>&lt;3&gt;</code> 处，调用我们传入的实现的 <code>RedisCallback#doInRedis(RedisConnection connection)</code> 方法，执行在 Pipeline 中，想要执行的 Redis 操作。</li><li><code>&lt;4&gt;</code> 处，不要返回结果。因为 RedisCallback 是统一定义的接口，所以可以返回一个结果。但是在 Pipeline 中，未提交执行时，显然是没有结果，返回也没有意思。简单来说，就是我们在实现 <code>RedisCallback#doInRedis(RedisConnection connection)</code> 方法时，返回 <code>null</code> 即可。</li><li><code>&lt;5&gt;</code> 处，调用 <code>RedisConnection#closePipeline()</code> 方法，<strong>自动</strong>提交 Pipeline 执行，并返回执行结果。</li><li><code>&lt;6&gt;</code> 处，反序列化结果，并返回 Pipeline 结果。</li></ul><p>至此，Spring Data Redis 对 Pipeline 的封装，我们已经做了一个简单的了解，实际就是经典的<a href="http://www.iocoder.cn/DesignPattern/xiaomingge/Template-Method/?vip" target="_blank" rel="noopener">“模板方法”</a>设计模式化的应用。下面，在让我们来看看 <a href="https://github.com/spring-projects/spring-data-redis/blob/2eb7067e8c7e859168a281145cc46ccddb42049f/src/main/java/org/springframework/data/redis/core/RedisCallback.java" target="_blank" rel="noopener">RedisCallback</a> 接口，Redis 回调接口。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisCallback.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">RedisCallback</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/**</span></span><br><span class="line"><span class="comment">	 * Gets called by &#123;<span class="doctag">@link</span> RedisTemplate&#125; with an active Redis connection. Does not need to care about activating or</span></span><br><span class="line"><span class="comment">	 * closing the connection or handling exceptions.</span></span><br><span class="line"><span class="comment">	 *</span></span><br><span class="line"><span class="comment">	 * <span class="doctag">@param</span> connection active Redis connection</span></span><br><span class="line"><span class="comment">	 * <span class="doctag">@return</span> a result object or &#123;<span class="doctag">@code</span> null&#125; if none</span></span><br><span class="line"><span class="comment">	 * <span class="doctag">@throws</span> DataAccessException</span></span><br><span class="line"><span class="comment">	 */</span></span><br><span class="line">	<span class="meta">@Nullable</span></span><br><span class="line">	<span class="function">T <span class="title">doInRedis</span><span class="params">(RedisConnection connection)</span> <span class="keyword">throws</span> DataAccessException</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>虽然接口名是以 Callback 结尾，但是通过 <code>#doInRedis(RedisConnection connection)</code> 方法可以很容易知道，实际可以理解是 Redis Action ，想要执行的 Redis 操作。</p></li><li><p>有一点要注意，传入的 <code>connection</code> 参数是 RedisConnection 对象，它提供的 <code>&#39;low level&#39;</code> 更底层的 Redis API 操作。例如说：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisStringCommands.java</span></span><br><span class="line"><span class="comment">// RedisConnection 实现 RedisStringCommands 接口</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">byte</span>[] get(<span class="keyword">byte</span>[] key);</span><br><span class="line"></span><br><span class="line"><span class="function">Boolean <span class="title">set</span><span class="params">(<span class="keyword">byte</span>[] key, <span class="keyword">byte</span>[] value)</span></span>;</span><br></pre></td></tr></table></figure><ul><li>传入和返回的是二进制数组，实际就是 RedisTemplate 已经序列化的入参和会被反序列化的出参。</li></ul></li></ul><h3 id="5-1-2-具体示例"><a href="#5-1-2-具体示例" class="headerlink" title="5.1.2 具体示例"></a>5.1.2 具体示例</h3><p>创建 <code>PipelineTest</code> 单元测试类，编写代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span></span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">PipelineTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test01</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        List&lt;Object&gt; results = stringRedisTemplate.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">            <span class="comment">// set 写入</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">3</span>; i++) &#123;</span><br><span class="line">                connection.set(String.format(<span class="string">"calm:%d"</span>, i).getBytes(), <span class="string">"handsome"</span>.getBytes());</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// get</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">3</span>; i++) &#123;</span><br><span class="line">                connection.get(String.format(<span class="string">"calm:%d"</span>, i).getBytes());</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 返回 null 即可</span></span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 打印结果</span></span><br><span class="line">        System.out.println(results);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行 <code>#test01()</code> 方法，结果如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[true, true, true, handsome, handsome, handsome]</span><br></pre></td></tr></table></figure><ul><li>因为我们使用 StringRedisTemplate 自己的序列化相关属性，所以 Redis GET 命令返回的二进制，被反序列化成了字符串。</li></ul><h2 id="5-2-Transaction"><a href="#5-2-Transaction" class="headerlink" title="5.2 Transaction"></a>5.2 Transaction</h2><blockquote><p>基情提示：实际项目实战中，Redis Transaction 事务基本不用，至少问了一些胖友，包括自己，都没有再用。所以呢，本小节可以选择性看看。或者，就不看，哈哈哈哈。</p></blockquote><p>在看 Redis Transaction 事务之前，我们先回想下 Spring 是如何管理<strong>数据库 Transaction</strong> 的。在应用程序中处理一个请求时，如果我们的方法开启Trasaction 功能，Spring 会把数据库的 Connection 连接和当前线程进行绑定，从而实现 Connection 打开一个 Transaction 后，所有当前线程的数据库操作都在该 Connection 上执行，达到所有操作在这个 Transaction 中，最终提交或回滚。</p><p>在 Spring Data Redis 中，实现 Redis Transaction 也是这个思路。通过 SessionCallback 操作 Redis 时，会从当前线程获得 Redis Connection ，如果获取不到，则会去“创建”一个 Redis Connection 并绑定到当前线程中。这样，我们在该 Redis Connection 开启 Redis Transaction 后，在该线程的所有操作，都可以在这个 Transaction 中，最后交由 Spring 事务管理器统一提供或回滚 Transaction 。</p><p>如果想要使用 Redis Transaction 功能，需要创建 RedisTemplate Bean 时，设置其 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/RedisTemplate.java#L91" target="_blank" rel="noopener">enableTransactionSupport</a> 属性为 <code>true</code> ，默认为 <code>false</code> 不开启。示例如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisConfiguration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 支持事务的 RedisTemplate</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span>(name = <span class="string">"redisTransactionTemplate"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> RedisTemplate&lt;String, Object&gt; <span class="title">redisTransactionTemplate</span><span class="params">(RedisConnectionFactory factory)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 创建 RedisTemplate 对象</span></span><br><span class="line">        RedisTemplate&lt;String, Object&gt; template = <span class="keyword">new</span> RedisTemplate&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设置开启事务支持</span></span><br><span class="line">        template.setEnableTransactionSupport(<span class="keyword">true</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设置 RedisConnection 工厂。😈 它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友，可以自己去撸下。</span></span><br><span class="line">        template.setConnectionFactory(factory);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 使用 String 序列化方式，序列化 KEY 。</span></span><br><span class="line">        template.setKeySerializer(RedisSerializer.string());</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 使用 JSON 序列化方式（库是 Jackson ），序列化 VALUE 。</span></span><br><span class="line">        template.setValueSerializer(RedisSerializer.json());</span><br><span class="line">        <span class="keyword">return</span> template;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-1-源码解析"><a href="#5-2-1-源码解析" class="headerlink" title="5.2.1 源码解析"></a>5.2.1 源码解析</h3><p>概念和原理层面的东西，一旦复杂，就会特别抽象，那么还是老规矩，让我们一起撸下源码，让原理具象化。很多时候，这就是为什么我们要去撸源码的意义。</p><p>我们先来看看，配置下 <code>enableTransactionSupport</code> 属性，Redis 在执行命令，是如何获得 Connection 连接的。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisTemplate.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> &lt;T&gt; <span class="function">T <span class="title">execute</span><span class="params">(RedisCallback&lt;T&gt; action, <span class="keyword">boolean</span> exposeConnection, <span class="keyword">boolean</span> pipeline)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	Assert.isTrue(initialized, <span class="string">"template not initialized; call afterPropertiesSet() before using it"</span>);</span><br><span class="line">	Assert.notNull(action, <span class="string">"Callback object must not be null"</span>);</span><br><span class="line"></span><br><span class="line">	RedisConnectionFactory factory = getRequiredConnectionFactory();</span><br><span class="line">	RedisConnection conn = <span class="keyword">null</span>;</span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">		<span class="comment">// &lt;1.1&gt;</span></span><br><span class="line">		<span class="keyword">if</span> (enableTransactionSupport) &#123;</span><br><span class="line">			<span class="comment">// only bind resources in case of potential transaction synchronization</span></span><br><span class="line">			conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);</span><br><span class="line">		&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">			<span class="comment">// &lt;1.2&gt;</span></span><br><span class="line">			conn = RedisConnectionUtils.getConnection(factory);</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="comment">// ... 省略中间，执行 Redis 命令的代码。</span></span><br><span class="line">	&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">		<span class="comment">// &lt;2&gt;</span></span><br><span class="line">		RedisConnectionUtils.releaseConnection(conn, factory);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>考虑到尽量让内容简单一些，我们不会对每一行代码做特别的深究，主要是保证胖友对 Spring Data Redis 对 Transaction 的封装，有个总体了解。</p></li><li><p><code>&lt;1.2&gt;</code> 处，当我们<strong>未</strong>开启 <code>enableTransactionSupport</code> 事务时，调用 <a href="https://github.com/spring-projects/spring-data-redis/blob/64b89137648f6c0e0c810c624e481bcfc0732f4e/src/main/java/org/springframework/data/redis/core/RedisConnectionUtils.java#L81" target="_blank" rel="noopener">RedisConnectionUtils#getConnection(factory)</a> 方法，获得 Redis Connection 。如果获取不到，则进行创建。</p></li><li><p><code>&lt;1.1&gt;</code>处，当我们<strong>有</strong>开启 <code>enableTransactionSupport</code> 事务时，调用</p><p><code>RedisConnectionUtils#bindConnection(RedisConnectionFactory factory, boolean enableTranactionSupport)</code>方法，在<code>RedisConnectionUtils#getConnection(factory)</code></p><p>的基础上，如果是创建的 Redis Connection ，会绑定到当前啊线程中。因为 Transaction 是需要在 Connection 打开，然后后续的 Redis 的操作，都需要在其上。并且，还有一个非常重要的操作，打开 Redis Transaction ，会在该方法中，通过调用 [`RedisConnectionUtils#potentiallyRegisterTransactionSynchronisation](<a href="https://github.com/spring-projects/spring-data-redis/blob/64b89137648f6c0e0c810c624e481bcfc0732f4e/src/main/java/org/springframework/data/redis/core/RedisConnectionUtils.java#L163" target="_blank" rel="noopener">https://github.com/spring-projects/spring-data-redis/blob/64b89137648f6c0e0c810c624e481bcfc0732f4e/src/main/java/org/springframework/data/redis/core/RedisConnectionUtils.java#L163</a>) 。</p></li><li><p><code>&lt;2&gt;</code> 处，调用 <a href="https://github.com/spring-projects/spring-data-redis/blob/64b89137648f6c0e0c810c624e481bcfc0732f4e/src/main/java/org/springframework/data/redis/core/RedisConnectionUtils.java#L206" target="_blank" rel="noopener">RedisConnectionUtils#releaseConnection(RedisConnection conn, RedisConnectionFactory factory)</a> 方法，释放 Redis Connection 。当然，这是有一个前提，整个 Transaction 已经完成。如果未完成，实际 Redis Connection 不会释放。</p></li></ul><p>那么，此时会有胖友有疑问，Redis Transaction 的提交和回滚在哪呢？答案在 RedisConnectionUtils 的内部类 RedisTransactionSynchronizer 中。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisConnectionUtils.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisTransactionSynchronizer</span> <span class="keyword">extends</span> <span class="title">TransactionSynchronizationAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">final</span> RedisConnectionHolder connHolder;</span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">final</span> RedisConnection connection;</span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">final</span> RedisConnectionFactory factory;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterCompletion</span><span class="params">(<span class="keyword">int</span> status)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			<span class="keyword">switch</span> (status) &#123;</span><br><span class="line">				<span class="comment">// 提交</span></span><br><span class="line">				<span class="keyword">case</span> TransactionSynchronization.STATUS_COMMITTED:</span><br><span class="line">					connection.exec();</span><br><span class="line">					<span class="keyword">break</span>;</span><br><span class="line">				<span class="comment">// 回滚</span></span><br><span class="line">				<span class="keyword">case</span> TransactionSynchronization.STATUS_ROLLED_BACK:</span><br><span class="line">				<span class="keyword">case</span> TransactionSynchronization.STATUS_UNKNOWN:</span><br><span class="line">				<span class="keyword">default</span>:</span><br><span class="line">					connection.discard();</span><br><span class="line">			&#125;</span><br><span class="line">		&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">			connHolder.setTransactionSyncronisationActive(<span class="keyword">false</span>);</span><br><span class="line">			connection.close();</span><br><span class="line">			TransactionSynchronizationManager.unbindResource(factory);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>根据事务结果的状态，进行 Redis Transaction 提交或回滚。如果想进一步的深入，胖友就需要去了解 Spring Transaction 的源码。</li></ul><h3 id="5-2-2-具体示例"><a href="#5-2-2-具体示例" class="headerlink" title="5.2.2 具体示例"></a>5.2.2 具体示例</h3><p>创建 TransactionTest 单元测试类，编写代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TransactionTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span></span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">TransactionTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(value = <span class="string">"redisTransactionTemplate"</span>)</span><br><span class="line">    <span class="keyword">private</span> RedisTemplate redisTransactionTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test01</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 执行想要的操作</span></span><br><span class="line">        redisTransactionTemplate.opsForValue().set(<span class="string">"calm:1"</span>, <span class="string">"handsome"</span>);</span><br><span class="line">        redisTransactionTemplate.opsForValue().set(<span class="string">"code:1"</span>, <span class="string">"hard"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>目前这仅仅是一个示例，实际并不生效。因为 Redis Transaction <strong>实际</strong>创建事务的前提，是当前已经存在 Spring Transaction 。</p><h3 id="5-2-3-补充资料"><a href="#5-2-3-补充资料" class="headerlink" title="5.2.3 补充资料"></a>5.2.3 补充资料</h3><p>如果觉得还是无法理解的胖友，可以在看看如下几篇文章：</p><ul><li><a href="https://blog.csdn.net/zlfprogram/article/details/75196156" target="_blank" rel="noopener">《Spring Data Redis(Redis Transactions)》</a></li><li><a href="https://blog.csdn.net/qq_32331073/article/details/79899762" target="_blank" rel="noopener">《Redis 之坑：spring-data-redis 中的 Redis 事务》</a></li><li><a href="https://www.cnblogs.com/softidea/p/5720938.html" target="_blank" rel="noopener">《Spring Data Redis 事务专题》</a></li></ul><h3 id="5-2-4-闲话两句"><a href="#5-2-4-闲话两句" class="headerlink" title="5.2.4 闲话两句"></a>5.2.4 闲话两句</h3><p>实际场景下，如果胖友有 Redis 事务的诉求，建议把事务的、和非事务的 RedisTemplate 拆成两个连接池，相互独立。主要原因有两个：</p><ul><li>1）Spring Data Redis 的事务设计，是将其融入到 Spring 整个 Transaction 当中。一般来说，Spring Transaction 中，肯定会存在数据库的 Transaction 。考虑到数据库操作相比 Redis 来说，肯定是慢得多，那么就会导致 Redis 的 Connection 一直被当前 Transaction 占用着。</li><li>2）<a href="https://stackoverflow.com/questions/34992256/how-can-i-eliminate-getting-junk-value-through-redis-get-command" target="_blank" rel="noopener">How can i eliminate getting junk value through redis get command?</a></li></ul><h2 id="5-3-Session"><a href="#5-3-Session" class="headerlink" title="5.3 Session"></a>5.3 Session</h2><p>首先，我们需要澄清下，Session 不是 Redis 的功能，而是 Spring Data Redis 封装的一个功能。一次 Session ，代表通过同一个 Redis Connection 执行一系列的 Redis 操作。</p><p>在 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/">「5.2.1 源码解析」</a> 中，我们可以发现，如果我们在一个 Redis Transaction 中的时候，所有 Redis 操作都使用通过同一个 Redis Connection ，因为我们会将获得到的 Connection 绑定到当前线程中。</p><p>但是，如果我们不在一个 Redis Transaction 中的时候，我们每一次使用 Redis Operations 执行 Redis 操作的时候，每一次都会获取一次 Redis Connection 的获取。实际项目中，我们必然会使用 Redis Connection 连接池，那么在获取的时候，会存在一定的竞争，会有资源上的消耗。那么，如果我们希望如果已知我们要执行一个系列的 Redis 操作，能不能使用同一个 Redis Connection ，避免重复获取它呢？答案是有，那就是 Session 。</p><p>当我们要执行在同一个 Session 里的操作时，我们通过实现 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/SessionCallback.java" target="_blank" rel="noopener">SessionCallback</a> 接口，其代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SessionCallback.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">SessionCallback</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Nullable</span></span><br><span class="line">	&lt;K, V&gt; <span class="function">T <span class="title">execute</span><span class="params">(RedisOperations&lt;K, V&gt; operations)</span> <span class="keyword">throws</span> DataAccessException</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>相比 RedisCallback 来说，总比是比较相似的。但是比较友好的是，它的入参 <code>operations</code> 是 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/RedisOperations.java" target="_blank" rel="noopener">RedisOperations</a> 接口类型，而 RedisTemplate 的各种操作，实际就是在 RedisOperations 接口中定义，由 RedisTemplate 来实现。所以使用上也会更加便利。</li><li>实际上，我们在实现 RedisCallback 接口，也能实现在同一个 Connection 执行一系列的 Redis 操作，因为 RedisCallback 的入参本身就是一个 Redis Connection 。</li></ul><h3 id="5-3-1-源码解析"><a href="#5-3-1-源码解析" class="headerlink" title="5.3.1 源码解析"></a>5.3.1 源码解析</h3><p>在生产中，Transaction 和 Pipeline 会经常一起时候用，从而提升性能。所以在 <code>RedisTemplate#executePipelined(SessionCallback session, ...)</code> 方法中，提供了这种的功能。而在这个方法的实现上，本质和 <code>RedisTemplate#executePipelined(RedisCallback action, ...)</code> 方法是基本一致的，差别在于<a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/core/RedisTemplate.java#L289" target="_blank" rel="noopener">这一行</a> ，替换成了调用 <code>#executeSession(SessionCallback session)</code> 方法。所以，我们来直接来看被调用的这个方法的实现。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisTemplate.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> &lt;T&gt; <span class="function">T <span class="title">execute</span><span class="params">(SessionCallback&lt;T&gt; session)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	Assert.isTrue(initialized, <span class="string">"template not initialized; call afterPropertiesSet() before using it"</span>);</span><br><span class="line">	Assert.notNull(session, <span class="string">"Callback object must not be null"</span>);</span><br><span class="line"></span><br><span class="line">	RedisConnectionFactory factory = getRequiredConnectionFactory();</span><br><span class="line">	<span class="comment">// bind connection</span></span><br><span class="line">	<span class="comment">// &lt;1&gt; 获得并绑定 Connection 。</span></span><br><span class="line">	RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);</span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">	   <span class="comment">// &lt;2&gt; 执行定义的一系列 Redis 操作</span></span><br><span class="line">		<span class="keyword">return</span> session.execute(<span class="keyword">this</span>);</span><br><span class="line">	&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">		<span class="comment">// &lt;3&gt; 释放并解绑 Connection 。</span></span><br><span class="line">		RedisConnectionUtils.unbindConnection(factory);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1&gt;</code>处，调用 <code>RedisConnectionUtils#bindConnection(RedisConnectionFactory factory, boolean enableTranactionSupport)</code> 方法，实际和我们开启 <code>enableTranactionSupport</code>事务时候，获取 Connection 和处理的方式，是一模一样的。也就是说：<ul><li>如果当前线程已经有一个绑定的 Connection 则直接使用（例如说，当前正在 Redis Transaction 事务中）；</li><li>如果当前线程未绑定一个 Connection ，则进行创建并绑定到当前线程。甚至，如果此时是配置开启 <code>enableTranactionSupport</code> 事务的，那么此处就会触发 Redis Transaction 的开启。</li></ul></li><li><code>&lt;2&gt;</code> 处，调用 <code>SessionCallback#execute(RedisOperations operations)</code> 方法，执行我们定义的一系列的 Redis 操作。看看此处传入的参数是 <code>this</code> ，是不是仿佛更加明白点什么了？</li><li><code>&lt;3&gt;</code> 处，调用 <a href="https://github.com/spring-projects/spring-data-redis/blob/64b89137648f6c0e0c810c624e481bcfc0732f4e/src/main/java/org/springframework/data/redis/core/RedisConnectionUtils.java#L253" target="_blank" rel="noopener">RedisConnectionUtils#unbindConnection(RedisConnectionFactory factory)</a> 方法，释放并解绑 Connection 。当前，前提是当前不存在激活的 Redis Transaction ，不然不就提早释放了嘛。</li></ul><p>恩，现在胖友在回过头，好好在想一想 Pipeline、Transaction、Session 之间的关系，以及组合排列。之后，我们在使用上，会更加得心应手。</p><h3 id="5-3-2-具体示例"><a href="#5-3-2-具体示例" class="headerlink" title="5.3.2 具体示例"></a>5.3.2 具体示例</h3><p>创建 <code>SessionTest</code> 单元测试类，编写代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SessionTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span></span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">SessionTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test01</span><span class="params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        String result = stringRedisTemplate.execute(<span class="keyword">new</span> SessionCallback&lt;String&gt;() &#123;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> String <span class="title">execute</span><span class="params">(RedisOperations operations)</span> <span class="keyword">throws</span> DataAccessException </span>&#123;</span><br><span class="line">                <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">                    operations.opsForValue().set(String.format(<span class="string">"calm:session:%d"</span>, i), <span class="string">"handsome"</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> (String) operations.opsForValue().get(String.format(<span class="string">"calm:session:%d"</span>, <span class="number">0</span>));</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">"result:"</span> + result);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行 <code>#test01()</code> 方法，结果如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">result:handsome</span><br></pre></td></tr></table></figure><ul><li>卧槽，一直被 Redis 夸奖，已经超级不好意思了。</li></ul><h2 id="5-4-Pub-Sub"><a href="#5-4-Pub-Sub" class="headerlink" title="5.4 Pub/Sub"></a>5.4 Pub/Sub</h2><p>Redis 提供了 Pub/Sub 功能，实现简单的订阅功能，不了解的胖友，可以看看 <a href="http://redis.cn/topics/pubsub.html" target="_blank" rel="noopener">「Redis 文档 —— Pub/Sub」</a> 。</p><h3 id="5-4-1-源码解析"><a href="#5-4-1-源码解析" class="headerlink" title="5.4.1 源码解析"></a>5.4.1 源码解析</h3><p>暂时不提供，感兴趣的胖友，可以自己看看最核心的 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/listener/RedisMessageListenerContainer.java" target="_blank" rel="noopener">RedisMessageListenerContainer</a> 类，Redis 消息监听器容器，基于 Pub/Sub 的 <a href="http://redis.cn/commands/subscribe.html" target="_blank" rel="noopener">SUBSCRIBE</a>、<a href="http://redis.cn/commands/psubscribe.html" target="_blank" rel="noopener">PSUBSCRIBE</a> 命令实现，我们只需要添加相应的 <a href="https://github.com/spring-projects/spring-data-redis/blob/64b89137648f6c0e0c810c624e481bcfc0732f4e/src/main/java/org/springframework/data/redis/connection/MessageListener.java" target="_blank" rel="noopener">MessageListener</a> 即可。不算复杂，1000 多行，只要调试下核心的功能即可。</p><h3 id="5-4-2-具体示例"><a href="#5-4-2-具体示例" class="headerlink" title="5.4.2 具体示例"></a>5.4.2 具体示例</h3><p>Spring Data Redis 实现 Pub/Sub 的示例，主要分成两部分：</p><ul><li>配置 RedisMessageListenerContainer Bean 对象，并添加我们自己实现的 MessageListener 对象，用于监听处理相应的消息。</li><li>使用 RedisTemplate 发布消息。</li></ul><p>下面，我们通过四个<strong>小</strong>步骤，来实现一个简单的示例。</p><p><strong>第一步，了解 Topic</strong></p><p><a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/listener/Topic.java" target="_blank" rel="noopener">Topic</a> 接口，表示 Redis 消息的 Topic 。它有两个子类实现：</p><ul><li>ChannelTopic ：对应 <a href="http://redis.cn/commands/subscribe.html" target="_blank" rel="noopener">SUBSCRIBE</a> 订阅命令。</li><li>PatternTopic ：对应 <a href="http://redis.cn/commands/psubscribe.html" target="_blank" rel="noopener">PSUBSCRIBE</a> 订阅命令。</li></ul><p><strong>第二步，实现 MessageListener 类</strong></p><p>创建 <code>TestChannelTopicMessageListener</code> 类 和 <code>TestPatternTopicMessageListener</code>，编写代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TestChannelTopicMessageListener</span></span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestChannelTopicMessageListener</span> <span class="keyword">implements</span> <span class="title">MessageListener</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onMessage</span><span class="params">(Message message, <span class="keyword">byte</span>[] pattern)</span> </span>&#123;</span><br><span class="line">        log.info(<span class="string">"收到 ChannelTopic 消息："</span>);</span><br><span class="line">        log.info(<span class="string">"线程编号：&#123;&#125;, message：&#123;&#125;, pattern：&#123;&#125;"</span>, Thread.currentThread().getName(), message, <span class="keyword">new</span> String(pattern));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*----------------------------------------------------*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TestPatternTopicMessageListener</span></span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestPatternTopicMessageListener</span> <span class="keyword">implements</span> <span class="title">MessageListener</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onMessage</span><span class="params">(Message message, <span class="keyword">byte</span>[] pattern)</span> </span>&#123;</span><br><span class="line">        log.info(<span class="string">"收到 PatternTopic 消息："</span>);</span><br><span class="line">        log.info(<span class="string">"线程编号：&#123;&#125;, message：&#123;&#125;, pattern：&#123;&#125;"</span>, Thread.currentThread().getName(), message, <span class="keyword">new</span> String(pattern));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>message</code> 参数，可获得到具体的消息内容，不过是二进制数组，需要我们自己序列化。具体可以看下 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/connection/DefaultMessage.java" target="_blank" rel="noopener">DefaultMessage</a> 类。</li><li><code>pattern</code> 参数，订阅的的 Topic 具体名称或者匹配符。</li></ul><p>有一点要注意，默认的 RedisMessageListenerContainer 情况下，MessageListener 是<strong>并发</strong>消费，在线程池中执行(具体见<a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/listener/RedisMessageListenerContainer.java#L982-L988" target="_blank" rel="noopener">传送门</a>代码)。所以如果想相同 MessageListener <strong>串行</strong>消费，可以在方法上加 <code>synchronized</code> 修饰，来实现同步。</p><p><strong>第三步，创建 RedisMessageListenerContainer Bean</strong></p><p>在 RedisConfiguration 中，配置 RedisMessageListenerContainer Bean 。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RedisConfiguration.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> RedisMessageListenerContainer <span class="title">listenerContainer</span><span class="params">(RedisConnectionFactory factory)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 创建 RedisMessageListenerContainer 对象</span></span><br><span class="line">  RedisMessageListenerContainer container = <span class="keyword">new</span> RedisMessageListenerContainer();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 设置 RedisConnection 工厂。它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友，可以自己去撸下。</span></span><br><span class="line">  container.setConnectionFactory(factory);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 添加监听器</span></span><br><span class="line">  <span class="comment">//        container.addMessageListener(new TestChannelTopicMessageListener(), new ChannelTopic("TEST_PUB_SUB"));</span></span><br><span class="line">  <span class="comment">//        container.addMessageListener(new TestChannelTopicMessageListener(), new ChannelTopic("AOTEMAN"));</span></span><br><span class="line">  container.addMessageListener(<span class="keyword">new</span> TestPatternTopicMessageListener(), <span class="keyword">new</span> PatternTopic(<span class="string">"TEST*"</span>));</span><br><span class="line">  <span class="keyword">return</span> container;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>要注意，虽然 RedisConnectionFactory 可以多次调用 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/listener/RedisMessageListenerContainer.java#L375-L396" target="_blank" rel="noopener">#addMessageListener(MessageListener listener, Topic topic)</a> 方法，但是一定要都是相同的 Topic 类型。例如说，添加了 ChannelTopic 类型，就不能添加 PatternTopic 类型。为什么呢？因为 RedisMessageListenerContainer 是基于<strong>一次</strong> <a href="http://redis.cn/commands/subscribe.html" target="_blank" rel="noopener">SUBSCRIBE</a> 或 <a href="http://redis.cn/commands/psubscribe.html" target="_blank" rel="noopener">PSUBSCRIBE</a> 命令，所以不支持<strong>不同类型</strong>的 Topic 。当然，如果是<strong>相同类型</strong>的 Topic ，多个 MessageListener 是支持的。</p><ul><li>实际测试是可以同时添加 ChannelTopic 和 PatternTopic 类型的，但是发两三条消息就会丢信息，所以胖友不要这样做，看得见的风险</li></ul><p>那么，可能会有胖友会问，如果我添加了 <code>&quot;Test&quot;</code> 给 MessageListener<strong>A</strong> ，<code>&quot;AOTEMAN&quot;</code> 给 MessageListener<strong>B</strong> ，两个 Topic 是怎么分发（Dispatch）的呢？在 RedisMessageListenerContainer 中，有个 <a href="https://github.com/spring-projects/spring-data-redis/blob/master/src/main/java/org/springframework/data/redis/listener/RedisMessageListenerContainer.java#L961-L988" target="_blank" rel="noopener">DispatchMessageListener</a> 分发器，负责将不同的 Topic 分发到配置的 MessageListener 中。看到此处，有木有想到 Spring MVC 的 DispatcherServlet 分发不同的请求到对应的 <code>@RequestMapping</code> 方法。</p><p><strong>第四步，使用 RedisTemplate 发布消息</strong></p><p>创建 <code>PubSubTest</code> 测试类，编写代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span></span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">PubSubTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String TOPIC = <span class="string">"TEST_PUB_SUB"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test01</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">3</span>; i++) &#123;</span><br><span class="line">            stringRedisTemplate.convertAndSend(TOPIC, <span class="string">"calm:pubsub:"</span> + i);</span><br><span class="line">            Thread.sleep(<span class="number">1000L</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>通过 <code>RedisTemplate#convertAndSend(String channel, Object message)</code> 方法，PUBLISH 消息。</li></ul><p>执行 <code>#test01()</code> 方法，运行结果如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">11.775</span>  INFO <span class="number">25522</span> --- [enerContainer-<span class="number">2</span>] .s.r.j.l.TestPatternTopicMessageListener : 收到 PatternTopic 消息：</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">11.776</span>  INFO <span class="number">25522</span> --- [enerContainer-<span class="number">2</span>] .s.r.j.l.TestPatternTopicMessageListener : 线程编号：listenerContainer-<span class="number">2</span>, message：calm:pubsub:<span class="number">0</span>, pattern：TEST*</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">12.777</span>  INFO <span class="number">25522</span> --- [enerContainer-<span class="number">3</span>] .s.r.j.l.TestPatternTopicMessageListener : 收到 PatternTopic 消息：</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">12.778</span>  INFO <span class="number">25522</span> --- [enerContainer-<span class="number">3</span>] .s.r.j.l.TestPatternTopicMessageListener : 线程编号：listenerContainer-<span class="number">3</span>, message：calm:pubsub:<span class="number">1</span>, pattern：TEST*</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">13.778</span>  INFO <span class="number">25522</span> --- [enerContainer-<span class="number">4</span>] .s.r.j.l.TestPatternTopicMessageListener : 收到 PatternTopic 消息：</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">13.778</span>  INFO <span class="number">25522</span> --- [enerContainer-<span class="number">4</span>] .s.r.j.l.TestPatternTopicMessageListener : 线程编号：listenerContainer-<span class="number">4</span>, message：calm:pubsub:<span class="number">2</span>, pattern：TEST*</span><br></pre></td></tr></table></figure><ul><li>整整齐齐，发送和订阅都成功了。注意，<strong>线程编号</strong>。</li></ul><h3 id="5-4-3-闲话两句"><a href="#5-4-3-闲话两句" class="headerlink" title="5.4.3 闲话两句"></a>5.4.3 闲话两句</h3><blockquote><p>Redis 5.0 版本后，正式发布 Stream 功能，相信是有可能可以替代掉 Redis Pub/Sub 功能，提供可靠的消息订阅功能。</p></blockquote><p>上述的场景，「芋道源码」在使用 PUB/SUB 功能的时候，确实被这么坑过。当时他们的管理后台的权限，是缓存在 Java 进程当中，通过 Redis Pub/Sub 实现缓存的刷新。结果，当时某个 Java 节点网络出问题，恰好那个时候，有一条刷新权限缓存的消息 PUBLISH 出来，结果没刷新到。结果呢，运营在访问某个功能的时候，一会有权限（因为其他 Java 节点缓存刷新了），一会没有权限。</p><p>最近，「芋道源码」又去找了几个朋友请教了下，问问他们在生产环境下，是否使用 Redis Pub/Sub 功能，他们说使用 Kafka、或者 RocketMQ 的广播消费功能，更加可靠有保障。</p><p>对了，「芋道源码」有个管理系统里面有 Websocket 需要实时推送管理员消息，因为不知道管理员当前连接的是哪个 Websocket 服务节点，所以我们是通过 Redis Pub/Sub 功能，广播给所有 Websocket 节点，然后每个 Websocket 节点判断当前管理员是否连接的是它，如果是，则进行 Websocket 推送。因为之前网络偶尔出故障，会存在消息丢失，所以近期我们替换成了 RocketMQ 的广播消费，替代 Redis Pub/Sub 功能。</p><p>当然，不能说 Redis Pub/Sub 毫无使用的场景，以下卡姆来列举几个：</p><ul><li>1、在使用 Redis Sentinel 做高可用时，Jedis 通过 Redis Pub/Sub 功能，实现对 Redis 主节点的故障切换，刷新 Jedis 客户端的主节点的缓存。如果出现 Redis Connection 订阅的异常断开，会重新<strong>主动</strong>去 Redis Sentinel 的最新主节点信息，从而解决 Redis Pub/Sub 可能因为网络问题，丢失消息。</li><li>2、Redis Sentinel 节点之间的部分信息同步，通过 Redis Pub/Sub 订阅发布。</li><li>3、在我们实现 Redis 分布式锁时，如果获取不到锁，可以通过 Redis 的 Pub/Sub 订阅锁释放消息，从而实现其它获得不到锁的线程，快速抢占锁。当然，Redis Client 释放锁时，需要 PUBLISH 一条释放锁的消息。在 Redisson 实现分布式锁的源码中，我们可以看到。</li><li>4、Dubbo 使用 Redis 作为注册中心时，使用 Redis Pub/Sub 实现注册信息的同步。</li></ul><p>也就是说，如果想要有保障的使用 Redis Pub/Sub 功能，需要处理下发起订阅的 Redis Connection 的异常，例如说网络异常。然后，重新主动去查询最新的数据的状态。</p><h2 id="5-5-Script"><a href="#5-5-Script" class="headerlink" title="5.5 Script"></a>5.5 Script</h2><p>Redis 提供 Lua 脚本，满足我们希望组合排列使用 Redis 的命令，保证<strong>串行</strong>执行的过程中，不存在并发的问题。同时，通过将多个命令组合在同一个 Lua 脚本中，一次请求，直接处理，也是一个提升性能的手段。不了解的胖友，可以看看 <a href="http://redis.cn/documentation.html" target="_blank" rel="noopener">「Redis 文档 —— Lua 脚本」</a> 。</p><p><strong>第一步，编写 Lua 脚本</strong></p><p>创建 <code>resources/compareAndSet.lua</code> 脚本，实现 CAS 功能。代码如下：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> redis.call(<span class="string">'GET'</span>, KEYS[1]) ~= ARGV[1] <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">return</span> 0</span><br><span class="line">end</span><br><span class="line">redis.call(<span class="string">'SET'</span>, KEYS[1], ARGV[2])</span><br><span class="line"><span class="built_in">return</span> 1</span><br></pre></td></tr></table></figure><ul><li>第 1 到 3 行：判断 <code>KEYS[1]</code> 对应的 VALUE 是否为 <code>ARGV[1]</code> 值。如果不是（Lua 中不等于使用 <code>~=</code>），则直接返回 0 表示失败。</li><li>第 4 到 5 行：设置 <code>KEYS[1]</code> 对应的 VALUE 为新值 <code>ARGV[2]</code> ，并返回 1 表示成功。</li></ul><p><strong>第二步，调用 Lua 脚本</strong></p><p>创建 <code>ScriptTest</code> 测试类，编写代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span></span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">ScriptTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test01</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        <span class="comment">// 读取 /resources/lua/compareAndSet.lua 脚本 。注意，需要引入下 commons-io 依赖。</span></span><br><span class="line">        String scriptContents = IOUtils.toString(getClass().getResourceAsStream(<span class="string">"/lua/compareAndSet.lua"</span>), <span class="string">"UTF-8"</span>);</span><br><span class="line">        <span class="comment">// 创建 RedisScript 对象</span></span><br><span class="line">        RedisScript&lt;Long&gt; script = <span class="keyword">new</span> DefaultRedisScript&lt;&gt;(scriptContents, Long<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">        <span class="comment">// 执行 LUA 脚本</span></span><br><span class="line">        Long result = stringRedisTemplate.execute(script, Collections.singletonList(<span class="string">"calm:1"</span>), <span class="string">"handsome"</span>, <span class="string">"so handsome!"</span>);</span><br><span class="line">        log.info(result.toString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1.1&gt;</code> 行，读取 <code>/resources/lua/compareAndSet.lua</code> 脚本。注意，需要引入下 <code>commons-io</code> 依赖。</li><li><code>&lt;1.2&gt;</code> 行，创建 DefaultRedisScript 对象。第一个参数是脚本内容( <code>scriptSource</code> )，第二个是脚本执行返回值( <code>resultType</code> )。</li><li><code>&lt;2&gt;</code> 处，调用 <a href="https://github.com/spring-projects/spring-data-redis/blob/64b89137648f6c0e0c810c624e481bcfc0732f4e/src/main/java/org/springframework/data/redis/core/RedisTemplate.java#L342-L360" target="_blank" rel="noopener">RedisTemplate#execute(RedisScript script, List keys, Object… args)</a> 方法，发送 Redis 执行 LUA 脚本。</li></ul><p>最后，我们打印下执行结果。胖友可以自己执行下试试。</p><h1 id="6-尝试-Redisson"><a href="#6-尝试-Redisson" class="headerlink" title="6. 尝试 Redisson"></a>6. 尝试 Redisson</h1><blockquote><p>在 <a href="https://github.com/redisson/redisson-examples" target="_blank" rel="noopener">redisson-examples</a> 中，Redisson 官方提供了大量的示例。</p></blockquote><h2 id="6-1-快速入门"><a href="#6-1-快速入门" class="headerlink" title="6.1 快速入门"></a>6.1 快速入门</h2><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-data-access/labs-springboot2-redis/labs-springboot2-redis-redisson" target="_blank" rel="noopener">labs-springboot2-redis-redisson</a> 。</p></blockquote><h3 id="6-1-1-引入依赖"><a href="#6-1-1-引入依赖" class="headerlink" title="6.1.1 引入依赖"></a>6.1.1 引入依赖</h3><p>在 <code>pom.xml</code> 中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-redis-redisson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Redisson 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.redisson<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>redisson-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.11.3<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 方便等会写单元测试 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- Spring Data Redis 默认使用 Jackson 作为 JSON 序列化的工具 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.fasterxml.jackson.core<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jackson-databind<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>和 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/">「2.1 引入依赖」</a> 的差异点是，我们需要引入 <code>redisson-spring-boot-starter</code> 依赖，实现 Redisson 的自动化配置。</p><h3 id="6-1-2-配置文件"><a href="#6-1-2-配置文件" class="headerlink" title="6.1.2 配置文件"></a>6.1.2 配置文件</h3><p>在 <a href="https://github.com/YunaiV/SpringBoot-Labs/blob/master/lab-11/lab-07-spring-data-redis-with-redisson/src/main/resources/application.yml" target="_blank" rel="noopener">application.yml</a> 中，添加 Redis 配置，如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="comment"># 对应 RedisProperties 类</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line"><span class="comment">#    password: # Redis 服务器密码，默认为空。生产中，一定要设置 Redis 密码！</span></span><br><span class="line">    <span class="attr">database:</span> <span class="number">0</span> <span class="comment"># Redis 数据库号，默认为 0 。</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="number">0</span> <span class="comment"># Redis 连接超时时间，单位：毫秒。</span></span><br><span class="line">    <span class="comment"># 对应 RedissonProperties 类</span></span><br><span class="line">    <span class="attr">redisson:</span></span><br><span class="line">      <span class="attr">config:</span> <span class="string">classpath:redisson.yaml</span> <span class="comment"># 具体的每个配置项，见 org.redisson.config.Config 类。</span></span><br></pre></td></tr></table></figure><p>和 <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/">「2.2 配置文件」</a> 的差异点是：</p><p><strong>1）去掉 Jedis 相关的配置项</strong></p><p><strong>2) 去掉了 <code>spring.redis.password</code>，没有设置密码时，不能设置该项，深坑</strong></p><p><strong>3）增加 <code>redisson.config</code> 配置</strong></p><p>在我们使用 Spring Boot 整合 Redisson 时候，通过该配置项，引入一个外部的 Redisson 相关的配置文件。例如说，示例中，我们引入了 <code>classpath:redisson.yaml</code> 配置文件。它可以使用 JSON 或 YAML 格式，进行配置。</p><blockquote><p>FROM <a href="https://blog.csdn.net/zl_momomo/article/details/82788294" target="_blank" rel="noopener">《Spring Boot 2.x 整合 lettuce redis 和 redisson》</a> 文章。</p></blockquote><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">clusterServersConfig:</span></span><br><span class="line">  <span class="comment"># 连接空闲超时 如果当前连接池里的连接数量超过了最小空闲连接数，而同时有连接空闲时间超过了该数值，那么这些连接将会自动被关闭，并从连接池里去掉。时间单位是毫秒。</span></span><br><span class="line">  <span class="attr">idleConnectionTimeout:</span> <span class="number">10000</span></span><br><span class="line">  <span class="attr">pingTimeout:</span> <span class="number">1000</span></span><br><span class="line">  <span class="comment"># 连接超时</span></span><br><span class="line">  <span class="attr">connectTimeout:</span> <span class="number">10000</span></span><br><span class="line">  <span class="comment"># 命令等待超时</span></span><br><span class="line">  <span class="attr">timeout:</span> <span class="number">3000</span></span><br><span class="line">  <span class="comment"># 命令失败重试次数</span></span><br><span class="line">  <span class="attr">retryAttempts:</span> <span class="number">3</span></span><br><span class="line">  <span class="comment"># 命令重试发送时间间隔</span></span><br><span class="line">  <span class="attr">retryInterval:</span> <span class="number">1500</span></span><br><span class="line">  <span class="comment"># 重新连接时间间隔</span></span><br><span class="line">  <span class="attr">reconnectionTimeout:</span> <span class="number">3000</span></span><br><span class="line">  <span class="comment"># failedAttempts</span></span><br><span class="line">  <span class="attr">failedAttempts:</span> <span class="number">3</span></span><br><span class="line">  <span class="comment"># 密码</span></span><br><span class="line">  <span class="attr">password:</span> <span class="literal">null</span></span><br><span class="line">  <span class="comment"># 单个连接最大订阅数量</span></span><br><span class="line">  <span class="attr">subscriptionsPerConnection:</span> <span class="number">5</span></span><br><span class="line">  <span class="comment"># 客户端名称</span></span><br><span class="line">  <span class="attr">clientName:</span> <span class="literal">null</span></span><br><span class="line">  <span class="comment">#负载均衡算法类的选择  默认轮询调度算法RoundRobinLoadBalancer</span></span><br><span class="line">  <span class="attr">loadBalancer:</span> <span class="string">!&lt;org.redisson.connection.balancer.RoundRobinLoadBalancer&gt;</span> <span class="string">&#123;&#125;</span></span><br><span class="line">  <span class="attr">slaveSubscriptionConnectionMinimumIdleSize:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">slaveSubscriptionConnectionPoolSize:</span> <span class="number">50</span></span><br><span class="line">  <span class="comment"># 从节点最小空闲连接数</span></span><br><span class="line">  <span class="attr">slaveConnectionMinimumIdleSize:</span> <span class="number">32</span></span><br><span class="line">  <span class="comment"># 从节点连接池大小</span></span><br><span class="line">  <span class="attr">slaveConnectionPoolSize:</span> <span class="number">64</span></span><br><span class="line">  <span class="comment"># 主节点最小空闲连接数</span></span><br><span class="line">  <span class="attr">masterConnectionMinimumIdleSize:</span> <span class="number">32</span></span><br><span class="line">  <span class="comment"># 主节点连接池大小</span></span><br><span class="line">  <span class="attr">masterConnectionPoolSize:</span> <span class="number">64</span></span><br><span class="line">  <span class="comment"># 只在从服务节点里读取</span></span><br><span class="line">  <span class="attr">readMode:</span> <span class="string">"SLAVE"</span></span><br><span class="line">  <span class="comment"># 主节点信息</span></span><br><span class="line">  <span class="attr">nodeAddresses:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">"redis://192.168.56.128:7000"</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">"redis://192.168.56.128:7001"</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">"redis://192.168.56.128:7002"</span></span><br><span class="line">  <span class="comment">#集群扫描间隔时间 单位毫秒</span></span><br><span class="line">  <span class="attr">scanInterval:</span> <span class="number">1000</span></span><br><span class="line"><span class="attr">threads:</span> <span class="number">0</span></span><br><span class="line"><span class="attr">nettyThreads:</span> <span class="number">0</span></span><br><span class="line"><span class="attr">codec:</span> <span class="string">!&lt;org.redisson.codec.JsonJacksonCodec&gt;</span> <span class="string">&#123;&#125;</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong><br><strong>注意</strong><br><strong>注意</strong></p><p>如果 <code>redisson.config</code> 对应的配置文件，如果没有配置任何内容，需要在 <code>application.yml</code> 里注释掉 <code>redisson.config</code> 。像这样：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="comment"># 对应 RedisProperties 类</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line"><span class="comment">#    password: # Redis 服务器密码，默认为空。生产中，一定要设置 Redis 密码！</span></span><br><span class="line">    <span class="attr">database:</span> <span class="number">0</span> <span class="comment"># Redis 数据库号，默认为 0 。</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="number">0</span> <span class="comment"># Redis 连接超时时间，单位：毫秒。</span></span><br><span class="line">    <span class="comment"># 对应 RedissonProperties 类</span></span><br><span class="line"><span class="comment">#    redisson:</span></span><br><span class="line"><span class="comment">#      config: classpath:redisson.yml # 具体的每个配置项，见 org.redisson.config.Config 类。</span></span><br></pre></td></tr></table></figure><h3 id="6-1-3-简单测试"><a href="#6-1-3-简单测试" class="headerlink" title="6.1.3 简单测试"></a>6.1.3 简单测试</h3><p>创建 <code>RedissonTest</code> 测试类，我们来测试一下简单的 SET 指令。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span></span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">RedissonTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Autowired</span></span><br><span class="line">	<span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Test</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testStringSetKey</span><span class="params">()</span> </span>&#123;</span><br><span class="line">		stringRedisTemplate.opsForValue().set(<span class="string">"calm:redisson:testStringSetKey"</span>, <span class="string">"handsome"</span>);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们先来执行下 <code>#testStringSetKey()</code> 方法这个测试方法。执行完成后，我们在控制台查询，看看是否真的执行成功了。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ redis-cli get calm:redisson:testStringSetKey</span><br><span class="line"><span class="string">"handsome"</span></span><br></pre></td></tr></table></figure><h3 id="6-1-4-闲聊两句"><a href="#6-1-4-闲聊两句" class="headerlink" title="6.1.4 闲聊两句"></a>6.1.4 闲聊两句</h3><p>因为有 Spring Data Redis 的存在，我们其实已经能感受到，即使我们将 Jedis 替换成了 Redisson ，依然调用的是相同的 Spring Data Redis 提供的 API ，而无需感知到 Redisson 或是 Jedis 的存在。如果哪一天，Spring Boot 2.X 版本默认推荐的 Lettuce 真的成熟了，那么我们也可以无感知的进行替换。</p><h2 id="6-2-Redis-分布式锁"><a href="#6-2-Redis-分布式锁" class="headerlink" title="6.2 Redis 分布式锁"></a>6.2 Redis 分布式锁</h2><p>一说到分布式锁，大家一般会想到的就是基于 Zookeeper 或是 Redis 实现分布式锁。相对来说，在考虑性能为优先因素，不需要特别绝对可靠性的场景下，我们会优先考虑使用 Redis 实现的分布式锁。</p><p>在 Redisson 中，提供了 8 种分布式锁的实现，具体胖友可以看看 <a href="https://github.com/redisson/redisson/wiki/8.-分布式锁和同步器" target="_blank" rel="noopener">《Redisson 文档 —— 分布式锁和同步器》</a> 。真特码的强大！大多数开发者可能连 Redis 怎么实现分布式锁都没完全搞清楚，Redisson 直接给了 8 种锁，气人，简直了。</p><p>本小节，我们来编写一个简单使用 Redisson 提供的可重入锁 RLock 的示例。</p><p>创建 <code>LockTest</code>测试类，编写代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span></span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">LockTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String LOCK_KEY = <span class="string">"anylock"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redissonClient;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        <span class="comment">// 启动一个线程 A ，去占有锁</span></span><br><span class="line">        <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">            <span class="comment">// 加锁以后 10 秒钟自动解锁</span></span><br><span class="line">            <span class="comment">// 无需调用 unlock 方法手动解锁</span></span><br><span class="line">            <span class="keyword">final</span> RLock lock = redissonClient.getLock(LOCK_KEY);</span><br><span class="line">            lock.lock(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">        &#125;).start();</span><br><span class="line">        <span class="comment">// 简单 sleep 1 秒，保证线程 A 成功持有锁</span></span><br><span class="line">        Thread.sleep(<span class="number">1000L</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 尝试加锁，最多等待 100 秒，上锁以后 10 秒自动解锁</span></span><br><span class="line">        log.info(<span class="string">"准备开始获得锁时间：&#123;&#125;"</span>, <span class="keyword">new</span> SimpleDateFormat(<span class="string">"yyyy-MM-DD HH:mm:ss"</span>).format(<span class="keyword">new</span> Date()));</span><br><span class="line">        <span class="keyword">final</span> RLock lock = redissonClient.getLock(LOCK_KEY);</span><br><span class="line">        <span class="keyword">boolean</span> res = lock.tryLock(<span class="number">100</span>, <span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">        <span class="keyword">if</span> (res) &#123;</span><br><span class="line">            log.info(<span class="string">"实际获得锁时间：&#123;&#125;"</span>, <span class="keyword">new</span> SimpleDateFormat(<span class="string">"yyyy-MM-DD HH:mm:ss"</span>).format(<span class="keyword">new</span> Date()));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            log.info(<span class="string">"加锁失败"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>整个测试用例，意图是：1）启动一个线程 A ，先去持有锁 10 秒然后释放；2）主线程，也去尝试去持有锁，因为线程 A 目前正在占用着该锁，所以需要等待线程 A 释放到该锁，才能持有成功。</li><li><code>&lt;1&gt;</code> 处，注入 RedissonClient 对象。因为我们需要使用 Redisson 独有的功能，所以需要使用到它。</li><li><code>&lt;2.1&gt;</code> 处，启动线程 A ，然后调用 <code>RLock#lock(long leaseTime, TimeUnit unit)</code> 方法，加锁以后 10 秒钟自动解锁，无需调用 unlock 方法手动解锁。</li><li><code>&lt;2.2&gt;</code> 处，简单 sleep 1 秒，保证线程 A 成功持有锁。</li><li><code>&lt;3&gt;</code> 处，主线程，调用 <code>RLock#tryLock(long waitTime, long leaseTime, TimeUnit unit)</code> 方法，尝试加锁，最多等待 100 秒，上锁以后 10 秒自动解锁。</li></ul><p>执行 <code>#test()</code> 测试用例，结果如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">51</span>:<span class="number">26</span><span class="variable">.601</span>  INFO <span class="number">25540</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.redis</span><span class="variable">.redisson</span><span class="variable">.LockTest</span>          : 准备开始获得锁时间：<span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">51</span>:<span class="number">26</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">51</span>:<span class="number">35</span><span class="variable">.677</span>  INFO <span class="number">25540</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.redis</span><span class="variable">.redisson</span><span class="variable">.LockTest</span>          : 实际获得锁时间：<span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">51</span>:<span class="number">35</span></span><br></pre></td></tr></table></figure><ul><li>9 秒后（因为我们 sleep 了 1 秒），主线程成功获得到 Redis 分布式锁，符合预期。</li></ul><h2 id="6-3-Redis-限流器"><a href="#6-3-Redis-限流器" class="headerlink" title="6.3 Redis 限流器"></a>6.3 Redis 限流器</h2><p>在开始本节之前，先推荐看一篇干货 <a href="http://www.iocoder.cn/Fight/How-do-you-robust-back-end-services/?vip" target="_blank" rel="noopener">《你应该如何正确健壮后端服务？》</a> 。</p><p>限流，无论在系统层面，还是在业务层面，使用都非常广泛。例如说：</p><ul><li>【业务】为了避免恶意的灌水机或者用户，限制每分钟至允许回复 10 个帖子。</li><li>【系统】为了避免服务系统被大规模调用，超过极限，限制每个调用方只允许每秒调用 100 次。</li></ul><p>每一种的概念，推荐看看 <a href="https://www.iphpt.com/detail/106" target="_blank" rel="noopener">《计数器、滑动窗口、漏桶、令牌算法比较和伪代码实现》</a> 文章。</p><ul><li><p>比较简单，每<strong>固定单位</strong>一个计数器即可实现。</p></li><li><p>Redisson 提供的是基于<strong>滑动窗口</strong> RateLimiter 的实现。相比<strong>计数器</strong>的实现，它的起点不是固定的，而是以开始计数的那个时刻开始为一个窗口。</p><p>所以，我们可以把<strong>计数器</strong>理解成一个滑动窗口的特例，以<strong>固定单位</strong>为一个窗口。</p></li><li><p><a href="http://www.iocoder.cn/Eureka/rate-limiter/?vip" target="_blank" rel="noopener">《Eureka 源码解析 —— 基于令牌桶算法的 RateLimiter》</a> ，单机并发场景下的 RateLimiter 实现。</p></li><li><p><a href="http://www.iocoder.cn/Spring-Cloud-Gateway/filter-request-rate-limiter/?vip" target="_blank" rel="noopener">《Spring-Cloud-Gateway 源码解析 —— 过滤器 (4.10) 之 RequestRateLimiterGatewayFilterFactory 请求限流》</a> ，基于 Redis 实现的令牌桶算法的 RateLimiter 实现。</p></li><li><p>漏桶算法，一直没搞明白和令牌桶算法的区别。现在的理解是：</p><ul><li>令牌桶算法，桶里装的是令牌。每次能拿取到令牌，就可以进行访问。并且，令牌会按照速率不断恢复放到令牌桶中直到桶满。</li><li>漏桶算法，桶里装的是请求。当桶满了，请求就进不来。例如说，Hystrix 使用线程池或者 Semaphore 信号量，只有在请求未满的时候，才可以进行执行。</li></ul></li></ul><p>上面哔哔了非常多的字，只看本文的话，就那一句话：“<strong>Redisson 提供的是基于滑动窗口 RateLimiter 的实现。</strong>”。</p><h3 id="6-3-1-具体示例"><a href="#6-3-1-具体示例" class="headerlink" title="6.3.1 具体示例"></a>6.3.1 具体示例</h3><p>创建 <code>RateLimiterTest</code> 测试类，编写代码如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">@Slf4j</span><br><span class="line">@RunWith(SpringRunner.class)</span><br><span class="line">@SpringBootTest</span><br><span class="line">public class RateLimiterTest &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private RedissonClient redissonClient;</span><br><span class="line"></span><br><span class="line">    @Test</span><br><span class="line">    public void test() throws InterruptedException &#123;</span><br><span class="line">        &#x2F;&#x2F; 创建 RRateLimiter 对象</span><br><span class="line">        RRateLimiter rateLimiter &#x3D; redissonClient.getRateLimiter(&quot;myRateLimiter&quot;);</span><br><span class="line">        &#x2F;&#x2F; 初始化：最大流速 &#x3D; 每 1 秒产生 2 个令牌</span><br><span class="line">        rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);</span><br><span class="line">&#x2F;&#x2F;        rateLimiter.trySetRate(RateType.PER_CLIENT, 50, 1, RateIntervalUnit.MINUTES);</span><br><span class="line"></span><br><span class="line">        SimpleDateFormat simpleDateFormat &#x3D; new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;);</span><br><span class="line">        for (int i &#x3D; 0; i &lt; 5; i++) &#123;</span><br><span class="line">            log.info(&quot;&#123;&#125;：获得锁结果(&#123;&#125;)&quot;, simpleDateFormat.format(new Date()), rateLimiter.tryAcquire());</span><br><span class="line">            Thread.sleep(250L);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行 <code>#test()</code> 测试用例，结果如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">53</span>:<span class="number">50</span><span class="variable">.819</span>  INFO <span class="number">25548</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.redis</span><span class="variable">.redisson</span><span class="variable">.RateLimiterTest</span>   : <span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">53</span>:<span class="number">50</span>：获得锁结果(true)</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">53</span>:<span class="number">51</span><span class="variable">.076</span>  INFO <span class="number">25548</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.redis</span><span class="variable">.redisson</span><span class="variable">.RateLimiterTest</span>   : <span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">53</span>:<span class="number">51</span>：获得锁结果(true)</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">53</span>:<span class="number">51</span><span class="variable">.332</span>  INFO <span class="number">25548</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.redis</span><span class="variable">.redisson</span><span class="variable">.RateLimiterTest</span>   : <span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">53</span>:<span class="number">51</span>：获得锁结果(false)</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">53</span>:<span class="number">51</span><span class="variable">.585</span>  INFO <span class="number">25548</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.redis</span><span class="variable">.redisson</span><span class="variable">.RateLimiterTest</span>   : <span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">53</span>:<span class="number">51</span>：获得锁结果(false)</span><br><span class="line"><span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">53</span>:<span class="number">51</span><span class="variable">.841</span>  INFO <span class="number">25548</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.redis</span><span class="variable">.redisson</span><span class="variable">.RateLimiterTest</span>   : <span class="number">2020</span>-<span class="number">01</span>-<span class="number">09</span> <span class="number">22</span>:<span class="number">53</span>:<span class="number">51</span>：获得锁结果(true)</span><br></pre></td></tr></table></figure><ul><li>第 1、2 次，成功获取锁。</li><li>第 3、4 次，因为每 1 秒产生 2 个令牌，所以被限流了。</li><li>第 5 次，已经过了 1 秒，所以获得令牌成功。</li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>在高并发场景下，系统会大量依赖缓存和消息队列，实现所需要的高性能。而缓存，绝大部分的选择，基本都是 Redis ，这点毋庸置疑。所以，我们是非常有必要深入去学习下 Redis ，友情推荐下付磊大佬的 <a href="https://u.jd.com/lDNJa9" target="_blank" rel="noopener">《Redis 开发与运维》</a> 。</p><p>文章篇幅很大，一些源码虽然很短，但是很绕，能吃下去就吃，吃不下去优先跑通demo就好。</p><p>推荐阅读：</p><ul><li><a href="http://www.iocoder.cn/Performance-Testing/Redis-benchmark/?self" target="_blank" rel="noopener">《性能测试 —— Redis 基准测试》</a></li><li><a href="http://www.iocoder.cn/Spring-Boot/Cache/?self" target="_blank" rel="noopener">《芋道 Spring Boot 缓存 Cache 入门》</a></li></ul></div><div class="article-footer"><blockquote class="mt-2x"><ul class="post-copyright list-unstyled"><li class="post-copyright-link hidden-xs"><strong>本文链接：</strong> <a href="https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/" title="Spring Boot Redis 入门" target="_blank" rel="external">https://calm_java.gitee.io/blog/2020/01/09/labs-springboot2-redis/</a></li><li class="post-copyright-license"><strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external">CC BY 4.0 CN协议</a> 许可协议。转载请注明出处！</li></ul></blockquote><div class="panel panel-default panel-badger"><div class="panel-body"><figure class="media"><div class="media-left"><a href="https://github.com/yuyong725" target="_blank" class="img-burn thumb-sm visible-lg"><img src="/blog/images/avatar.jpg" class="img-rounded w-full" alt=""></a></div><div class="media-body"><h3 class="media-heading"><a href="https://github.com/yuyong725" target="_blank"><span class="text-dark">卡姆</span><small class="ml-1x">a Java coder need calm</small></a></h3><div>享受编程</div></div></figure></div></div></div></article><section id="comments"></section></div><nav class="bar bar-footer clearfix" data-stick-bottom><div class="bar-inner"><ul class="pager pull-left"><li class="prev"><a href="/blog/2020/02/13/labs-springboot2-dynamic-datasource/" title="Spring Boot 多数据源（读写分离）入门"><i class="icon icon-angle-left" aria-hidden="true"></i><span>&nbsp;&nbsp;上一篇</span></a></li><li class="next"><a href="/blog/2020/01/09/labs-springboot2-cache/" title="Spring Boot 缓存 Cache 入门"><span>下一篇&nbsp;&nbsp;</span><i class="icon icon-angle-right" aria-hidden="true"></i></a></li><li class="toggle-toc"><a class="toggle-btn" data-toggle="collapse" href="#collapseToc" aria-expanded="true" title="文章目录" role="button"><span>[&nbsp;</span><span>文章目录</span> <i class="text-collapsed icon icon-anchor"></i> <i class="text-in icon icon-close"></i> <span>]</span></a></li></ul><button type="button" class="btn btn-fancy btn-donate pop-onhover bg-gradient-warning" data-toggle="modal" data-target="#donateModal"><span>赏</span></button><div class="bar-right"><div class="share-component" data-sites="wechat" data-mobile-sites="wechat"></div></div></div></nav><div class="modal modal-center modal-small modal-xs-full fade" id="donateModal" tabindex="-1" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content donate"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button><div class="modal-body"><div class="donate-box"><div class="donate-head"><p>感谢您的支持，我会继续努力的!</p></div><div class="tab-content"><div role="tabpanel" class="tab-pane fade active in" id="alipay"><div class="donate-payimg"><img src="/blog/images/donate/alipayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开支付宝扫一扫，即可进行扫码打赏哦</p></div><div role="tabpanel" class="tab-pane fade" id="wechatpay"><div class="donate-payimg"><img src="/blog/images/donate/wechatpayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开微信扫一扫，即可进行扫码打赏哦</p></div></div><div class="donate-footer"><ul class="nav nav-tabs nav-justified" role="tablist"><li role="presentation" class="active"><a href="#alipay" id="alipay-tab" role="tab" data-toggle="tab" aria-controls="alipay" aria-expanded="true"><i class="icon icon-alipay"></i> 支付宝</a></li><li role="presentation"><a href="#wechatpay" role="tab" id="wechatpay-tab" data-toggle="tab" aria-controls="wechatpay" aria-expanded="false"><i class="icon icon-wepay"></i> 微信支付</a></li></ul></div></div></div></div></div></div></main><footer class="footer" itemscope itemtype="http://schema.org/WPFooter"></footer><script src="//cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script><script>window.jQuery||document.write('<script src="js/jquery.min.js"><\/script>')</script><script src="/blog/js/plugin.min.js"></script><script src="/blog/js/application.js"></script><script>!function(T){var N={TRANSLATION:{POSTS:"文章",PAGES:"页面",CATEGORIES:"分类",TAGS:"标签",UNTITLED:"(未命名)"},ROOT_URL:"/blog/",CONTENT_URL:"/blog/content.json"};T.INSIGHT_CONFIG=N}(window)</script><script src="/blog/js/insight.js"></script><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script src="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.js"></script><script src="//cdn.jsdelivr.net/npm/blueimp-md5@2.10.0/js/md5.min.js"></script><script type="text/javascript">var gitalk=new Gitalk({clientID:"adf0c8bfdd8796029917",clientSecret:"a88c2e2e83e2b180f62ad927dcb347871dfe9f4b",repo:"myblog-comment",owner:"yuyong725",admin:["yuyong725"],id:md5(location.pathname),distractionFreeMode:!0});gitalk.render("comments")</script></body></html><!-- rebuild by neat -->